行为型模式
问题
一个对象的行为,如果受它自身状态的影响,在调用该对象的方法时,不同状态下同一个方法产生不同的结果,看起来就仿佛这个对象修改了自己的类。
在这种情况下,就需要使用“状态模式”。
组成
状态模式的组成 UML 如下:
图中的组成如下:
- Context:持有一个 ConcreteState 实例的对象,直接被调用和请求。
- State(状态):一个抽象类或者接口,提供了一些与 Context 状态相关的行为,比如 Context 提供了 open\close 方法,Satete 会提供这类的方法。
- ConcreteState:State 的具体实现类,每个 ConcreteState 实现 State 的一个状态相关的行为。
当客户端的请求到达时,Context 把与状态相关的调用请求,委托给自己持有的 ConcreteState 对象处理。
Context 可以把自身作为一个参数传递给 ConcreteState,这样 ConcreteState 在必要的时候可以访问 Context,修改 Context 的属性值等。
当 ConcreteState 执行完具体逻辑后,可以决定是否需要做状态转换,如果需要转换状态,返回后继状态(另一个 ConcreteState 实例)给 Context,Context 修改自己持有的ConcreteState 引用。
应用场景
状态模式适用的场景如下:
1、一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为。
2、一个操作中含有庞大的多分支的条件语句,这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。
State 模式把每个条件分支放入到一个独立的 ConcreteState 类中,使得对象的状态成为一个独立的存在,可以不依赖于其他条件而变化,持有状态的对象可以根据需要把相关逻辑由状态来实现。
《设计模式:可复用面向对象软件的基础》以“TCPConnection”为例,把 TCPConnection 的状态抽象为一个独立的类“TCPState”来表示网络的连接状态,为不同状态子类声明了一个公共接口。
状态模式的优点和效果:
1、它把“与特定状态相关的行为”分割独立开来,分别由具体的类来实现,极大地增加了代码的扩展性;当增加新的状态以及“特定行为”后,通过定义新的 State 子类即可,避免了在 Context 中实现大片的条件判断语句。
2、它使得状态转换显式化,当 Context 以一个枚举类表示状态时,状态之间的变化被分散到不同的行为中,表现的不够明确;通过让 State 子类指定自己的后继状态可以何时进行状态,使得状态之间的转换逻辑更加内聚,且更加灵活。
3、它不会造成泄露 Context 和 State 的泄露,客户端在请求 Context 调用方法后,Context 调用自己持有的内部 State 对象,并把自己作为参数传入,而不是其他外部对象;客户端也不会直接与 State 交互,而是由 Context 来执行内部调用,有效地避免了 Context 和 State 泄露到外部。
示例代码
在工作中基本没有看到过与状态模式相关的代码示例,这个模式使用的确比较少,主要原因有两个:
1、之前我们解决“一个对象的行为取决于它的状态”这个问题时,更多地是使用策略模式来实现,不同的状态调用不同的策略对象。
在之前的业务开发过程中,遇到这类“同一个行为在不同状态下表现不一致”的问题,都是用策略模式来实现,比如计算作品积分的时候,不同的状态下计算结果不同,是把相关条件封装成一个对象,传入到 StrategyFactory 中,选择对应的 Strategy 来计算:作品 --> 封装对象 --> Strategy --> 计算结果。
这种做法也是没有问题的,我们是把“计算积分”这个行为从“作品”这个对象中抽出来,单独由一个 Strategy 类来提供,封装不同的计算逻辑。
当将来要增加新的“计算积分”逻辑时,只需要创建新的 Strategy 实现即可。
2、业务中遇到的“状态”与“状态模式中的状态”存在很大的差异,虽然从叫法上都是“状态”,但一个是“status”一个是“state”,指代的不是相同的含义。
在我们开发的采购系统中,存在着“需求单、询价单、验收单、订单”这些具体聚合根,它们也都有状态(status),并且 status 之间有着转换,转换逻辑还很复杂。
那么是否可以使用状态模式(State)来实现这些聚合根的 Status?
这个得看具体的业务逻辑描述,在我们的系统中,status 不是聚合根行为的条件,而是结果。比如,需求单有“待审批、已审批、审批未通过、已询价、询价中、未询价”等状态,这些 status 是需求单聚合根在执行完一系列行为后的结果,用 user case 来描述就是“需求单创建完询价单后从‘未询价’转换到‘询价中’”。这就和“状态模式”使用的场景不符,不是“行为受状态影响”,而是“状态是行为的结果”。
所以,我们并没有使用“状态模式”来解决遇到的 status 变化复杂的问题,而是通过把“status 转换逻辑封装到 staus 中”来解决。
这里引申出了一个问题:状态模式中的“状态 state”指代的是什么?
刚开始的时候,我的理解是“对象持有的一个 status 字段”,不同的 status 值表示对象有不同的状态。
这种理解是错误,实在是因为两者的叫法太相似,非常具有误导性。
后来理解为,“状态 state 是指对象持有的属性(field)”,对应的是“对象由‘状态’和行为组成”中的‘状态’,state 不同表示对象属性值不同。
那么是不是所有属性值都会影响对象的行为?并不是,对象的很多属性值对它的行为没有任何影响。
因此,状态模式中的“状态 state”指代的是:会影响对象行为的那些属性取值。
小结
前面提到了,我们有时会使用策略模式来代替状态模式,并且两者很相似:
- 状态模式:对象的不同状态下,行为会有不同的结果。
- 策略模式:不同的输入下,选择不同的策略,同一个方法产生不同的结果。
这两者的区别是什么?
两者要解决的问题(问题场景)不一样:
- 状态模式要解决的是,对象在运行时不同状态下行为的差异,避免大片的条件分支判断,强调的是“对象行为在不同状态下的变化”。
- 策略模式要解决的是,不同的参数如何选择不同的算法实现,强调的是“如何封装算法实现的替换和选择”。