设计模式(二十)状态
一、定义
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类,状态模式又称为状态对象,它是一种对象行为模式。
二、描述
状态模式是一种较为复杂的设计模式,用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题,包含以下三个角色:
1、Context(环境类):环境类又称为上下文类,它是拥有多种状态的对象。由于环境类的状态存在多样性,且在不同状态下对象的行为有所不同,所以将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象。
2、State(抽象状态类):它用于定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现了这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中。
3、ConcreteState(具体状态类):它是抽象状态类的子类,每一个具体状态类实现一个与环境类的一个状态相关的行为,对应环境类的一个具体状态,不同的具体状态类其行为有所不同。
三、例子
X公司公司要为一银行开发一套信用卡业务系统,银行账户(Account)是该系统的核心类之一,通过分析,系统中账户存在3种状态,根据余额的不同,以上3种状态可发生相互转换,具体说明如下:
(1)如果账户中余额大于等于0,则账户的状态为正常状态(Normal State),此时用户既可以向该账户存款也可以从该账户取款。
(2)如果账户中余额小于0,并且大于一2000,则账户的状态为透支状态(Overdraft State),此时用户既可以向该账户存款也可以从该账户取款,但需要按天计算利息。
(3)如果账户中余额等于-2000,那么账户的状态为受限状态(Restricted State),此时用户只能向该账户存款,不能再从中取款,同时也将按天计算利息。
Account:银行账户,充当环境类
public 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方法
}
}
AccountState:账户状态类,充当抽象状态类
public 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();
}
NormalState、OverdraftState、RestrictedState:正常状态、透支状态、受限状态,充当具体状态类
public 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("操作受限!");
}
}
}
public class OverdraftState : AccountState
{
public OverdraftState(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 > 0)
{
Acc.SetState(new NormalState(this));
}
else if (Acc.Balance == -2000)
{
Acc.SetState(new OverdraftState(this));
}
else if (Acc.Balance < -2000)
{
Console.WriteLine("操作受限!");
}
}
}
public class RestrictedState : AccountState
{
public RestrictedState(AccountState state)
{
this.Acc = state.Acc;
}
public override void Deposit(double amount)
{
Acc.Balance = Acc.Balance + amount;
StateCheck();
}
public override void Withdraw(double amount)
{
Console.WriteLine("账户受限,取款失败!");
}
public override void ComputeInterest()
{
Console.WriteLine("计算利息!");
}
public override void StateCheck()
{
if (Acc.Balance > 0)
{
Acc.SetState(new NormalState(this));
}
else if (Acc.Balance > -2000)
{
Acc.SetState(new OverdraftState(this));
}
}
}
Program:测试代码
Account acc = new Account("段誉", 0.0);
acc.Deposit(1000);
acc.Withdraw(2000);
acc.Deposit(3000);
acc.Withdraw(4000);
acc.Withdraw(1000);
acc.ComputeInterest();
Console.ReadLine();
四、总结
1、优点
(1)状态模式封装了状态的转换规则,在状态模式中可以将状态的转换代码封装在环境类或者具体状态类中,可以对状态转换代码进行集中管理,而不是分散在一个个业务方法中。
(2)状态模式将所有与某个状态有关的行为放到一个类中,只需注入一个不同的状态对象即可使环境对象拥有不同的行为。
(3)状态模式允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,状态模式可以避免使用庞大的条件语句将业务方法和状态转换代码交织在一起。
(4)状态模式可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
2、缺点
(1)状态模式会增加系统中类和对象的个数,导致系统运行开销增大。
(2)其结构与实现都较为复杂,如果使用不当将导致程序结构和代码混乱,增加系统设计的难度。
(3)状态模式对开闭原则的支持并不太好,增加新的状态类需要修改负责状态转换的源代码,否则无法转换到新增状态,而且修改某个状态类的行为也需要修改对应类的源代码。