声明:本系列为刘伟老师博客内容总结(http://blog.csdn.net/lovelion),博客中有完整的设计模式的相关博文,以及作者的出版书籍推荐
本系列内容思路分析借鉴了刘伟老师的博文内容,同时改用C#代码进行代码的演示和分析(Java资料过多 C#表示默哀).
本系列全部源码均在文末地址给出。
本系列开始讲解行为型模式,关注如何将现有类或对象组织在一起形成更加强大的结构。
11种常见的行为型模式
很多事物都具有多种状态,而且在不同状态下会具有不同的行为,这些状态在特定条件下还将发生相互转换。就像水,它可以凝固成冰,也可以受热蒸发后变成水蒸汽,水可以流动,冰可以雕刻,蒸汽可以扩散。
-状态模式 (State Pattern):允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
- 用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题
- 将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化
- 对于客户端而言,无须关心对象状态的转换以及对象所处的当前状态,无论对于何种状态的对象,客户端都可以一致处理
问题描述
- 银行系统中的账户类设计
菜鸟软件公司欲为某银行开发一套信用卡业务系统,银行账户(Account)是该系统的核心类之一,账户存在三种状态,且在不同状态下账户存在不同的行为,具体说明如下:
(1) 如果账户中余额大于等于0,则账户的状态为正常状态(Normal State),此时用户既可以向该账户存款也可以从该账户取款;
(2) 如果账户中余额小于0,并且大于-2000,则账户的状态为透支状态(Overdraft State),此时用户既可以向该账户存款也可以从该账户取款,但需要按天计算利息;
(3) 如果账户中余额等于-2000,那么账户的状态为受限状态(Restricted State),此时用户只能向该账户存款,不能再从中取款,同时也将按天计算利息;
(4) 根据余额的不同,以上三种状态可发生相互转换。
初步思路分析
NormalState表示正常状态,OverdraftState表示透支状态,RestrictedState表示受限状态,在这三种状态下账户对象拥有不同的行为,方法deposit()用于存款,withdraw()用于取款,computeInterest()用于计算利息,stateCheck()用于在每一次执行存款和取款操作后根据余额来判断是否要进行状态转换并实现状态转换,相同的方法在不同的状态中可能会有不同的实现。
UML类图
实例关键源代码
class Account {
private String state; //状态
private int balance; //余额
......
//存款操作
public void deposit() {
//存款
stateCheck();
}
//取款操作
public void withdraw() {
if (state.equalsIgnoreCase("NormalState") || state.equalsIgnoreCase("OverdraftState ")) {
//取款
stateCheck();
}
else {
//取款受限
}
}
//计算利息操作
public void computeInterest() {
if(state.equalsIgnoreCase("OverdraftState") || state.equalsIgnoreCase("RestrictedState ")) {
//计算利息
}
}
//状态检查和转换操作
public void stateCheck() {
if (balance >= 0) {
state = "NormalState";
}
else if (balance > -2000 && balance < 0) {
state = "OverdraftState";
}
else if (balance == -2000) {
state = "RestrictedState";
}
else if (balance < -2000) {
//操作受限
}
}
......
}
现有缺陷(未来变化)
(1) 几乎每个方法中都包含状态判断语句,以判断在该状态下是否具有该方法以及在特定状态下该方法如何实现,导致代码非常冗长,可维护性较差;
(2) 拥有一个较为复杂的stateCheck()方法,包含大量的if…else if…else…语句用于进行状态转换,代码测试难度较大,且不易于维护;
(3) 系统扩展性较差,如果需要增加一种新的状态,如冻结状态(Frozen State,在该状态下既不允许存款也不允许取款),需要对原有代码进行大量修改,扩展起来非常麻烦。
如何改进
为了解决这些问题,我们可以使用状态模式,在状态模式中,我们将对象在每一个状态下的行为和状态转移语句封装在一个个状态类中,通过这些状态类来分散冗长的条件转移语句,让系统具有更好的灵活性和可扩展性,状态模式可以在一定程度上解决上述问题
新的UML类图
Account充当环境类角色,AccountState充当抽象状态角色,NormalState、OverdraftState和RestrictedState充当具体状态角色。
改进的实例原代码
class Account
{
private AccountState state; //维持一个对抽象状态对象的引用
private string owner; //开户名
private double balance = 0; //账户余额
public Account(string owner, double init)
{
this.owner = owner;
this.balance = init;
this.state = new NormalState(this); //设置初始状态
Console.WriteLine("{0}开户,初始金额为{1}", this.owner ,init);
Console.WriteLine("---------------------------------------------");
}
public double Balance
{
get { return balance; }
set { balance = value; }
}
public void SetState(AccountState state)
{
this.state = state;
}
public void Deposit(double amount)
{
Console.WriteLine("{0}存款{1}", this.owner,amount);
state.Deposit(amount); //调用状态对象的Deposit()方法
Console.WriteLine("现在余额为{0}", this.Balance);
Console.WriteLine("现在帐户状态为{0}",this.state.GetType().ToString());
Console.WriteLine("---------------------------------------------");
}
public void Withdraw(double amount)
{
Console.WriteLine("{0}取款{1}",this.owner, amount);
state.Withdraw(amount); //调用状态对象的Withdraw()方法
Console.WriteLine("现在余额为{0}", this.Balance);
Console.WriteLine("现在帐户状态为{0}", this.state.GetType().ToString());
Console.WriteLine("---------------------------------------------");
}
public void ComputeInterest()
{
state.ComputeInterest(); //调用状态对象的ComputeInterest()方法
}
}
abstract class AccountState
{
//抽象的账号状态类
private Account acc;
public Account Acc
{
get { return acc; }
set { acc = value; }
}
public abstract void Deposit(double amount);
public abstract void Withdraw(double amount);
public abstract void ComputeInterest();
public abstract void StateCheck();
}
class NormalState : AccountState
{
//具体的账号状态类 其他两个省略
public NormalState(Account acc)
{
this.Acc = acc;
}
public NormalState(AccountState state)
{
this.Acc = state.Acc;
}
public override void Deposit(double amount)
{
Acc.Balance = Acc.Balance + amount;
StateCheck();
}
public override void Withdraw(double amount)
{
Acc.Balance = Acc.Balance - amount;
StateCheck();
}
public override void ComputeInterest()
{
Console.WriteLine("正常状态,无须支付利息!");
}
//状态转换
public override void StateCheck()
{
if (Acc.Balance > -2000 && Acc.Balance <= 0)
{
Acc.SetState(new OverdraftState(this));
}
else if (Acc.Balance == -2000)
{
Acc.SetState(new RestrictedState(this));
}
else if (Acc.Balance < -2000)
{
Console.WriteLine("操作受限!");
}
}
}
class Program
{
static void Main(string[] args)
{
Account acc = new Account("段誉", 0.0);
acc.Deposit(1000);
acc.Withdraw(2000);
acc.Deposit(3000);
acc.Withdraw(4000);
acc.Withdraw(1000);
acc.ComputeInterest();
Console.Read();
}
}
动机和意图
-当系统中某个对象存在多个状态,这些状态之间可以进行转换,而且对象在不同状态下行为不相同时可以使用状态模式。
一般结构
ConcreteState(具体状态类):每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。
状态模式UML类图
共享状态
在有些情况下,多个环境对象可能需要共享同一个状态
如果希望在系统中实现多个环境对象共享一个或多个状态对象,那么需要将这些状态对象定义为环境类的静态成员对象。静态成员变量只会初始化一次。
举例说明:
如果某系统要求两个开关对象要么都处于开的状态,要么都处于关的状态,在使用时它们的状态必须保持一致,开关可以由开转换到关,也可以由关转换到开。
实例源代码
class Switch
{
private static State state, onState, offState; //定义三个静态的状态对象
private String name;
public Switch(String name)
{
this.name = name;
onState = new OnState();
offState = new OffState();
state = onState;
}
public void setState(State mstate)
{
state = mstate;
}
public static State getState(String type)
{
if (type.SequenceEqual("on"))
{
return onState;
}
else
{
return offState;
}
}
//打开开关
public void on()
{
Console.WriteLine(name);
state.on(this);
}
//关闭开关
public void off()
{
Console.WriteLine(name);
state.off(this);
}
}
abstract class State
{
public abstract void on(Switch s);
public abstract void off(Switch s);
}
//打开状态
class OnState : State
{
public override void on(Switch s)
{
Console.WriteLine(“已经打开!”);
}
public override void off(Switch s)
{
Console.WriteLine("关闭!");
s.setState(Switch.getState("off"));
}
}
//关闭状态
class OffState : State
{
public override void on(Switch s)
{
Console.WriteLine("打开!");
s.setState(Switch.getState("on"));
}
public override void off(Switch s)
{
Console.WriteLine("已经关闭!");
}
}
class Program
{
static void Main(string[] args)
{
Switch s1, s2;
s1 = new Switch(“开关1”);
s2 = new Switch(“开关2”);
s1.on();
s2.on();
s1.off();
s2.off();
s2.on();
s1.on();
}
}
使用环境类实现状态转换
在状态模式中实现状态转换时,具体状态类可通过调用环境类Context的setState()方法进行状态的转换操作,也可以统一由环境类Context来实现状态的转换。
对于客户端而言,无须关心状态类,可以为环境类设置默认的状态类,将状态的转换工作交给环境类(或具体状态类)来完成,具体的转换细节对于客户端而言是透明的
可以通过环境类来实现状态转换,环境类作为一个状态管理器,统一实现各种状态之间的转换操作
举例:
用户单击“放大镜”按钮之后屏幕将放大一倍,再点击一次“放大镜”按钮屏幕再放大一倍,第三次点击该按钮后屏幕将还原到默认大小。
改进后的优点
(1) 封装了状态的转换规则,在状态模式中可以将状态的转换代码封装在环境类或者具体状态类中,可以对状态转换代码进行集中管理,而不是分散在一个个业务方法中。
(2) 将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为。
(3)可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
现存的缺点
(1) 状态模式的使用必然会增加系统中类和对象的个数,导致系统运行开销增大。
(2) 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,增加系统设计的难度。
(3) 状态模式对“开闭原则”的支持并不太好,增加新的状态类需要修改那些负责状态转换的源代码,否则无法转换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。
适用场景
(1) 对象的行为依赖于它的状态(如某些属性值),状态的改变将导致行为的变化。
(2) 在代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强。
在实际开发中,状态模式具有较高的使用频率,在工作流和游戏开发中状态模式都得到了广泛的应用,例如公文状态的转换、游戏中角色的升级等。
实例源代码
GitHub地址
百度云地址:链接: https://pan.baidu.com/s/1kUYKJiR 密码: bea7