在面向对象软件设计中,经常会遇到依赖于状态的对象。例如,一个 Person(人)对象是依赖于(情绪)状态的。在该对象高兴、郁闷、忧愁和愤怒的时候,行为大不相同;对于同样的外界刺激,反应也不同。这说明同样一个对象,状态不同,其行为也不同。在依赖于状态的对象中,对象的行为依赖于其状态。
与策略模式类似,状态模式(State Pattern)将不同状态下的行为封装在不同的类中,每个类代表一个状态。
01
状态模式的概念与机制
如果一个对象(类)是依赖于状态的,则程序员在描述该对象的类中,通常会使用许多条件语句来覆盖所有的条件以及在这些条件下的对象的行为。但这种方法对于比较复杂的状态判断容易产生错误。基于大量的条件语句的处理方法有很多不便之处。首先是增加一个新的状态将会导致对类的大量修改,因此该设计不容易扩展和维护;另外,状态转换不明显,也可能发生编程错误。一个有效的处理依赖于状态的设计是利用即将要讲的状态模式。状态模式可以有效地消除在客户程序中的条件语句并且使得状态转换非常清楚。
根据“四人帮”(Gang of Four,GOF)的《设计模式》一书,状态模式是允许一个对象在其内部状态改变时改变他的行为。这个对象看起来似乎修改了它的类。
状态模式将不同状态下的行为封装在一个层次类的不同子类中。下图为状态模式的结构类图。
状态模式的各组成部分及说明如下。
既然上面类图中的每个状态子类都代表一个状态,并且封装了在该状态下的行为 operation(),那么状态的改变到底要哪个类完成呢?事实上,根据实际设计的程序的具体情况,有两种改变状态的方式:一种是状态超类或者各个状态子类负责改变状态,另一种是 Context 类负责改变状态。
在下列情况下可以使用状态模式。
关于 Context 类的说明如下。
状态模式的优点如下。
02
关于状态模式的讨论
关于状态模式的可扩展性的讨论
状态模式的要点如下。
值得注意的是,以上描述没有说明哪个类负责创建状态类的对象,因此该描述是比较粗略的。事实上,由哪个类创建状态类的对象对于可扩展性是非常关键的。现在分别对各种情况进行讨论。
情况 1 标准的设计。在 Context 类中保持状态类 State 的引用;在状态类中也保持一个 Context 类的引用。可以在 Context 类中写一个类似 setStateObj(State s)的方法,然后由状态类(超类或者子类)的某个方法(例如,changeState()方法)根据现在的状态负责创建子类的对象并且调用 Context 类中的 setStateObj(State s)方法,将新创建的状态子类对象赋值给 Context 类中的 State 的引用。在此情况下,在 Context 类中可以没有针对于状态的条件语句。Context 类与 State 类的交互是双方向的,既存在 Context 类对 State 类的调用,又存在从 State 类到 Context 类的调用。导致 Context 类对 State 类有较高的耦合。但是,本设计的可扩展性仍然较好,因为,在添加了一个新的状态类的时候,不必修改 Context 类。
情况 2 层次结构状态模式,即 Client 类调用 Context 类,Context 类调用 State 类,不存在任何相反方向的调用。在状态类中(可以是超类),写一个根据现在的状态创建子类对象的方法 creteStateObj(),然后在 Context 类中负责调用该方法。因为该方法已经是当前状态的子类的对象,所以在 Context 类中,不必使用针对于状态的条件语句。事实上,此时 Context 类根本不知道所使用的对象是哪个状态子类对象,只知道是当前对象。注意,此时,Context 类与 State 类的交互是单方向的:仅存在 Context 类对 State 类的调用,而不存在从 State 类到 Context 类的调用。经过精心设计,可以使得 Client 类仅调用 Context,Context 类仅调用 State 类,而不存在任何其他的调用。该设计有很好的扩展性。
综上所述,在使用状态模式进行设计的时候,可扩展性方面一般达不到开闭原则,但是经过精心的设计,仍然可以大幅度的提高可扩展性。
策略模式和状态模式的相似之处
两种模式在结构上是相同的,策略模式将每个条件分支封装在一个子类中,而状态模式将每个状态封装在一个子类中。
策略模式和状态模式的区别
策略模式用来处理具有相同目的但是实现方法不同的算法,这些算法方案之间一般没有状态的变迁,并且用户总是从几个算法中间选取一个。例如,以不同的格式保存文件,以不同的算法压缩文件,以不同的算法排序,以不同的格式绘制同样数据的图形,以不同的方法显示信息,等等。
状态模式则有所不同,它实现的一个概念可以叫做动态继承,也就是继承的(代表一个状态)子类都可以发生变化。状态的变化可以由一个状态迁移图表示。在实现中,如果状态的变化由状态类完成,则在整个变化过程中某些状态之间需要插入一个新的状态,或者状态的顺序之间发生变化,客户程序是根本不用改变的,即对客户是可见的。
一般地说,使用状态模式要比使用策略模式在设计与实现方面会更加复杂一些,原因是用户需要仔细的考虑由谁负责状态转换问题,是由 Context 类负责还是由状态超类负责,还是由状态的子类负责等。
from abc import ABC, abstractmethod
class State(ABC):
@abstractmethod
def operation(self):
pass
class ConcreteStateA(State):
def operation(self):
print('ConcreteStateA')
class ConcreteStateB(State):
def operation(self):
print('ConcreteStateB')
class Context:
def __init__(self, state):
self.state = state
def request(self):
self.state.operation()
class Client:
@staticmethod
def main():
state = ConcreteStateA()
context = Context(state)
context.request()
state = ConcreteStateB()
context.state = state
context.request()
if __name__ == '__main__':
Client.main()
本文分享自 Python机器学习算法说书人 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!