设计模式学习之状态模式

状态模式(State)的定义

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。

状态模式(State)适用性

  1. 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
  2. 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。

这个状态通常用一个或多个枚举常量表示。通常,有多个操作包含这一相同的条件结构。State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。

状态模式(State)的参与者

  1. Context:定义客户感兴趣的接口。维护一个ConcreteState子类的实例,这个实例定义当前状态。
  2. State:定义一个接口以封装与Context的一个特定状态相关的行为。
  3. ConcreteStatesubclasses:每一子类实现一个与Context的一个状态相关的行为。


下图描绘了状态模式对应地类图:

653266-20160418152108273-1472856947

自己的感悟:

  1. Context中有state的引用,这些state是可以相互转化的
  2. 状态的子类中含有Context的引用,ConcreteStateA和ConcreteStateB并不是毫无关系的,在某个状态改变的时候,状态的子类在执行handle方法的时候会根据状态图来修改Context中引用的状态
  3. 当代码中出现了各种重复的switch或者自己都看着恶心的if…else时,可以考虑使用状态设计模式

状态模式地例子

糖果机[1]

假设我们现在有一个糖果机项目,那么我们知道正常一般糖果机提供给用户的行为有这么几种:投入硬币、转动曲柄、退出硬币几种行为;那么糖果机呢一般有这几中状态,待机状态、持有硬币的准备状态、运行状态即正在售出状态和初始状态 这么几种正常状态。 我们发现处于不同状态的时候,持有的行为是不一样的,图如下:

242b991e7b5e33f24ca156308a022e7e


电梯[2]

实体是电梯,这个大家一定不陌生。我们知道电梯主要有4种状态:电梯门关闭、电梯门打开、电梯上下运载、电梯停止。而且我们知道,电梯在门打开的时候,只能是关闭电梯门,不能是其他的任何操作。在学习状态模式之前,如果我们要编写这个逻辑,一定是长篇累读地 if … else … 。而且逻辑混乱,很难维护。当然,这里你可以使用 if … else …,因为电梯的这些状态基本是稳定的,不会有什么变动。而如果你的需求里,状态会不断更新,而你之前使用 if … else … 埋下的患根这时就会让你苦不堪言。


TCP连接状态

《设计模式》展示了一个简化版的TCP协议实现;TCP连接的状态有多种可能,状态之间的转换有相应的逻辑前提; 这是使用状态模式的场合:

State_eg


状态模式描述了对象状态的变化以及对象如何在每一种状态下表现出不同的行为。
状态模式的关键是引入了一个抽象类来专门表示对象的状态,这个类我们叫做抽象状态类,而对象的每一种具体状态类都继承了该类,并在不同具体状态类中实现了不同状态的行为,包括各种状态之间的转换。
在状态模式结构中需要理解环境类与抽象状态类的作用:

环境类实际上就是拥有状态的对象,环境类有时候可以充当状态管理器(State Manager)的角色,可以在环境类中对状态进行切换操作。
抽象状态类可以是抽象类,也可以是接口,不同状态类就是继承这个父类的不同子类,状态类的产生是由于环境类存在多个状态,同时还满足两个条件: 这些状态经常需要切换,在不同的状态下对象的行为不同。因此可以将不同对象下的行为单独提取出来封装在具体的状态类中,使得环境类对象在其内部状态改变时可以改变它的行为,对象看起来似乎修改了它的类,而实际上是由于切换到不同的具体状态类实现的。由于环境类可以设置为任一具体状态类,因此它针对抽象状态类进行编程,在程序运行时可以将任一具体状态类的对象设置到环境类中,从而使得环境类可以改变内部状态,并且改变行为。


优点

状态模式的优点

封装了转换规则。
枚举可能的状态,在枚举状态之前需要确定状态种类。
将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

缺点

状态模式的缺点

状态模式的使用必然会增加系统类和对象的个数。
状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
状态模式对“开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。

适用环境

在以下情况下可以使用状态模式:

对象的行为依赖于它的状态(属性)并且可以根据它的状态改变而改变它的相关行为。
代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,使客户类与类库之间的耦合增强。在这些条件语句中包含了对象的行为,而且这些条件对应于对象的各种状态。

模式应用

状态模式在工作流或游戏等类型的软件中得以广泛使用,甚至可以用于这些系统的核心功能设计,如在政府OA办公系统中,一个批文的状态有多种:尚未办理;正在办理;正在批示;正在审核;已经完成等各种状态,而且批文状态不同时对批文的操作也有所差异。使用状态模式可以描述工作流对象(如批文)的状态转换以及不同状态下它所具有的行为。

模式扩展


共享状态

在有些情况下多个环境对象需要共享同一个状态,如果希望在系统中实现多个环境对象实例共享一个或多个状态对象,那么需要将这些状态对象定义为环境的静态成员对象。
简单状态模式与可切换状态的状态模式
简单状态模式:简单状态模式是指状态都相互独立,状态之间无须进行转换的状态模式,这是最简单的一种状态模式。对于这种状态模式,每个状态类都封装与状态相关的操作,而无须关心状态的切换,可以在客户端直接实例化状态类,然后将状态对象设置到环境类中。如果是这种简单的状态模式,它遵循“开闭原则”,在客户端可以针对抽象状态类进行编程,而将具体状态类写到配置文件中,同时增加新的状态类对原有系统也不造成任何影响。
可切换状态的状态模式:大多数的状态模式都是可以切换状态的状态模式,在实现状态切换时,在具体状态类内部需要调用环境类Context的setState()方法进行状态的转换操作,在具体状态类中可以调用到环境类的方法,因此状态类与环境类之间通常还存在关联关系或者依赖关系。通过在状态类中引用环境类的对象来回调环境类的setState()方法实现状态的切换。在这种可以切换状态的状态模式中,增加新的状态类可能需要修改其他某些状态类甚至环境类的源代码,否则系统无法切换到新增状态。


状态模式允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象,状态模式是一种对象行为型模式。
状态模式包含三个角色:环境类又称为上下文类,它是拥有状态的对象,在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象,可以定义初始状态;抽象状态类用于定义一个接口以封装与环境类的一个特定状态相关的行为;具体状态类是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。
状态模式描述了对象状态的变化以及对象如何在每一种状态下表现出不同的行为。
状态模式的主要优点在于封装了转换规则,并枚举可能的状态,它将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为,还可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数;其缺点在于使用状态模式会增加系统类和对象的个数,且状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,对于可以切换状态的状态模式不满足“开闭原则”的要求。
状态模式适用情况包括:对象的行为依赖于它的状态(属性)并且可以根据它的状态改变而改变它的相关行为;代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,使客户类与类库之间的耦合增强。


总结

通过上面的例子,我们已经对状态模式有所了解,下面我们做一个总结,来回顾我们的状态模式:

1. 状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

理解:这个模式将状态封装成独立的类,并将动作委托到代表当前状态的对象,这就是说行为会随着内部状态而改变。"看起来好像修改了它的类"是什么意思呢?从客户的视角来看:如果说你使用的对象能够完全改变它的行为,那么你会觉得,这个对象实际上是从别的类实例化而来的。然而,实际上,你知道我们是在使用组合通过简单引用不同的状态对象来造成类改变的假象。

2. 状态模式要点

  1. 客户不会和状态进行交互,全盘了解状态是context的工作
  2. 在状态模式中,每个状态通过持有context的引用,来实现状态转移
  3. 使用状态模式总是会增加设计中类的数目,这是为了要获得程序可扩展性,弹性的代价,如果你的代码不是一次性的,后期可能会不断加入不同的状态,那么状态模式的设计是绝对值得的。【同时也是一个缺点】
  4. 状态类可以被多个context实例共享

3. 状态模式和策略模式对比

首先让我们来看看它们之间更多的相似之处:

  1. 添加新的状态或策略都很容易,而且不需要修改使用它们的Context对象。
  2. 它们都让你的代码符合OCP原则(软件对扩展应该是开发的,对修改应该是关闭的)。在状态模式和策略
  3. 模式中,Context对象对修改是关闭的,添加新的状态或策略,都不需要修改Context。
  4. 正如状态模式中的Context会有初始状态一样,策略模式同样有默认策略。
  5. 状态模式以不同的状态封装不同的行为,而策略模式以不同的策略封装不同的行为。
  6. 它们都依赖子类去实现相关行为。


两个模式的差别在于它们的"意图"不同:

状态模式帮助对象管理状态,我们将一群行为封装到状态对象中,context的行为随时可委托到那些状态中的一个。随着时间的流逝,当前状态在状态对象集合中游走改变,以反映context内部状态,因此,context的行为也会跟着改变。当要添加新的状态时,不需要修改原来代码添加新的状态类即可。 而策略模式允许Client选择不同的行为。通过封装一组相关算法,为Client提供运行时的灵活性。Client可以在运行时,选择任一算法,而不改变使用算法的context。一些流行的策略模式的例子是写那些使用算法的代码,例如加密算法、压缩算法、排序算法。客户通常主动指定context所要组合的策略对象是哪一个。


一句话:最根本的差异在于策略模式是在求解同一个问题的多种解法,这些不同解法之间毫无关联;状态模式则不同,状态模式要求各个状态之间有所关联,以便实现状态转移。

原文地址:https://www.cnblogs.com/tuhooo/p/9093737.html