《设计模式》— 行为型模式 — 状态模式

一、动机

考虑一个表示网络连接的类 TCPConnection。一个 TCPConnection 对象的状态处于若干不同状态之一:连接已建立、正在监听、连接已关闭。当一个 TCPConnection 对象收到其他对象的请求时,它根据自身的当前状态做出不同的反应。例如,一个 open 请求的结果依赖于该连接是否处于连接已关闭状态还是连接已建立状态。状态模式描述了 TCPConnection 如何在每一种状态下表现出不同的行为。

这一模式的关键思想是引入了一个称为 TCPState 的抽象类来表示网络的连接状态。TCPState 类为各表示不同的操作状态的子类声明了一个公共接口。TCPState 的子类实现与特定状态相关的行为。例如,TCPEstablishedTCPClosed 类分别实现了特定于 TCPConnection 的连接已建立状态和连接已关闭状态的行为。
在这里插入图片描述
TCPConnection 类维护一个表示 TCP 连接当前状态的状态对象,并将所有与状态相关的请求委托给这个状态对象。TCPConnection 使用 TCPState 的子类实例来执行特定于连接状态的操作。

一旦连接状态改变,TCPConnection 对象就会改变它所使用的状态对象。例如当连接已建立状态转为已关闭状态时,TCPConnection就会用一个 TCPClosed 实例来替代原来的 TCPEstablished 实例。

二、适用性

  • 一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为。
  • 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常,有多个操作包含着以相同的条件结构。State 模式将每一个条件分支放入一个独立的类。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。

三、结构

在这里插入图片描述

四、参与者

1、Context

  • 定义客户感兴趣的接口
  • 维护一个 ConcreteState 子类的实例,这个实例定义当前状态。

2、State

定义一个接口封装与 Context 的一个特定状态相关的行为。

3、Concrete State

每一个子类实现一个与 Context 相关的行为。

五、协作

  • Context 将与状态相关的请求委托给当前的 Concrete State 对象处理。
  • Context 可将自身作为一个参数传递给处理该请求的状态对象。这使得状态对象在必要时可访问 Context
  • Context 是客户使用的主要接口。客户可用状态对象来配置一个 Context,一旦一个 Context 配置完毕,它的客户不再需要直接与状态对象打交道。
  • ContextConcrete State 子类都可决定哪个状态是另外一个的后继者,以及是在何种条件下进行转换。

六、效果

1、将与特定状态相关的行为局部化,并且将不同状态的行为分割开来

状态模式将所有与一个特定的状态相关的行为都放入一个对象中。因为所有与状态相关的代码都存在与某个 State 子类中,所以通过定义新的子类可以很容易地增加新的状态和转换。

另一个方法是使用数据值定义内部状态并且让 Context 操作来显式地检查这些数据。但这样将会是整个 Context 的实现中遍布看起来很相似的条件语句。增加一个新的状态可能需要改变若干个操作,这就使得维护变得很复杂了。

状态模式避免了这个问题,但是可能会引入另一个问题,因为该模式将不同状态的行为分布在多个 State 子类中。这就增加了子类的数目,相对于单个类的实现来说不够紧凑。但是有许多状态时这样的分布实际上更好一些,否则需要使用更巨大的条件语句。

2、使得状态转换显式化

当一个对象仅以内部数据值来定义当前状态时,其状态仅表现为对一些变量的赋值,这不够明确。为不同的状态引入独立的对象使得转换变得更加明确。而且,状态对象可保证 Context 不会发生内部状态不一致的情况,因为从 Context 的角度来看,状态转换是原子的。

3、State 对象可被共享

如果 State 对象没有实例变量,即它们表示的状态完全以它们的类型来编码,那么各 Context 对象可以共享 State 对象。当状态以这种方式被共享时,它们必然是没有内部状态而只有行为的轻量级对象。

七、实现

1、谁定义转换

State 模式不指定哪个参与者定义状态转换准则。如果该准则是固定的,那么它们可以在 Context 中完全实现。然而若让 State 子类指定它们的后继状态以及何时进行转换,通常更灵活、更合适。这需要 Context 增加一个接口,让 State 对象显式地设置 Context 当前的状态。

这样做可以很容易扩展子类,符合开闭原则。但是每个 State 子类至少拥有一个其他子类的信息,这样就在各子类间产生了依赖。

2、基于表的另一种方法

我们可以使用表驱动的方式配置每个状态的后继状态:使用表将输入映射到状态转换。对每一个状态,一张表将每一个可能的输入映射到一个后继状态。

这样的好处在于:我们可以简单地通过更改表中的数据修改状态转换的规则
其缺点在于:

  • 对表的查找通常不如虚函数调用效率高。
  • 用统一的、表格的形式表示逻辑转换使得转换准则变得不够明确而难以理解。
  • 通常难以加入伴随状态转换的一些动作。

表驱动的状态机与状态模式的主要区别可以被总结如下:状态模式对状态相关的行为进行建模,而表驱动的方法着重于定义状态转换。

3、创建和销毁对象

一个常见的值得考虑的实现上的权衡是,究竟是仅当需要 State 对象时才创建它们并随后销毁它们,还是提前创建它们并且始终不销毁它们。

当将要进入的状态在运行时是不可知的,并且上下文不经常改变状态时,第一种选择较为可取。这种方法避免创建不会被用到的对象,尤其是当 State 对象存储大量的信息时。当状态改变很频繁时,第二种方法较好。因为状态之间的转换可能很频繁。我们可以一次预付创建各个对象的开销。但是这些 State 对象的保存又是另一个问题。

八、应用

很容易将状态模式和状态机或状态图联系到一起。但是使用静态类型语言中的多态性质很难模拟状态机的行为。我们尝试使用 C++ 实现翻译转义字符串的功能:

1、State

我们这里共设置四个 State,分别对应于初始状态,读取字符串状态,读取转义字符状态,结束当前读取状态。我们这里选择在各个 State 中维护状态转换。为了方便,我们这里没有进行异常处理。

(1)抽象类

抽象类中提供 ostringstream 对象用于保存翻译后的字符串,同时提供 getString 接口用于 context 获取该字符串。

#pragma once
#include <string>
#include <iostream>
#include <sstream>

class State
{
   
protected:
	std::ostringstream readStr;
public
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值