设计模式的艺术之道--状态模式
来源:互联网 发布:mac os x leopard 编辑:程序博客网 时间:2024/05/18 22:15
设计模式的艺术之道–状态模式
声明:本系列为刘伟老师博客内容总结(http://blog.csdn.net/lovelion),博客中有完整的设计模式的相关博文,以及作者的出版书籍推荐
本系列内容思路分析借鉴了刘伟老师的博文内容,同时改用C#代码进行代码的演示和分析(Java资料过多 C#表示默哀).
本系列全部源码均在文末地址给出。
本系列开始讲解行为型模式,关注如何将现有类或对象组织在一起形成更加强大的结构。
- 行为型模式(Behavioral Pattern)
关注系统中对象之间的交互,研究系统在运行时对象之间的相互通信与协作,进一步明确对象的职责
不仅仅关注类和对象本身,还重点关注它们之间的相互作用和职责划分 - 类行为型模式
使用继承关系在几个类之间分配行为,主要通过多态等方式来分配父类与子类的职责 - 对象行为型模式
使用对象的关联关系来分配行为,主要通过对象关联等方式来分配两个或多个类的职责
11种常见的行为型模式
状态模式–处理对象的多种状态及其相互转换
很多事物都具有多种状态,而且在不同状态下会具有不同的行为,这些状态在特定条件下还将发生相互转换。就像水,它可以凝固成冰,也可以受热蒸发后变成水蒸汽,水可以流动,冰可以雕刻,蒸汽可以扩散。
1.1定义
-状态模式 (State Pattern):允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
- 用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题
- 将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化
- 对于客户端而言,无须关心对象状态的转换以及对象所处的当前状态,无论对于何种状态的对象,客户端都可以一致处理
1.2情景实例
问题描述
- 银行系统中的账户类设计
菜鸟软件公司欲为某银行开发一套信用卡业务系统,银行账户(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(); } }
1.3模式分析
动机和意图
-当系统中某个对象存在多个状态,这些状态之间可以进行转换,而且对象在不同状态下行为不相同时可以使用状态模式。
一般结构
- 状态模式包含3个角色:
- Context(环境类):拥有多种状态的对象。其中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象。
- State(抽象状态类):在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法。
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
- 设计模式的艺术之道--状态模式
- 设计模式的艺术之道--设计模式的基本概念
- 设计模式的艺术之道--简单工厂模式
- 设计模式的艺术之道--工厂方法模式
- 设计模式的艺术之道--抽象工厂模式
- 设计模式的艺术之道--单例模式
- 设计模式的艺术之道--原型模式
- 设计模式的艺术之道--建造者模式
- 设计模式的艺术之道--适配器模式
- 设计模式的艺术之道--桥接模式
- 设计模式的艺术之道--组合模式
- 设计模式的艺术之道--外观模式
- 设计模式的艺术之道--装饰模式
- 设计模式的艺术之道--享元模式
- 设计模式的艺术之道--代理模式
- 设计模式的艺术之道--职责链模式
- 设计模式的艺术之道--命令模式
- 设计模式的艺术之道--中介者模式
- 多维数组的实现 (java 表示)
- Revit二次开发之创建斑马线【比目鱼原创】
- 关于友盟推送集成的一些问题获取不到deviceToken
- Shiro实际使用(实现各种实用的拦截器)
- mysql+php+smarty 时间格式转换
- 设计模式的艺术之道--状态模式
- springboot03SpringBootJPA-Hibernate
- MYSQL常用命令总结
- java获取excel中数据,并转化为JSON格式
- 数据库基础学习笔记2
- 使用maven命令安装jar到maven本地资源库
- 交叉检验的简介
- win7下WindowsForms_主要控件文件打不开“确保 Web 地址 //ieframe.dll/dnserrordiagoff.htm# 正确。 “
- 温度状态转化加迟滞处理