状态模式(State Pattern):拒绝大量的 If 与 Switch

版权声明:本文为博主学习笔记, 注明来源情况下随意转载 https://blog.csdn.net/lengxiao1993/article/details/73331404
  1. 参考书籍: 《Design Patterns: Elements of Reusable Object-Oriented Software》

设计模式用前须知

大部分程序员编写的程序可以分为三类(应用程序、工具包 、框架),使用设计模式的目的是提高代码的可复用性和可扩展性(灵活性), 但是设计模式在这三类软件中所发挥的效果是不一样的。

很多有经验的程序员会得出“使用了设计模式,反而降低了代码的可读性,增加了复杂度”的结论, 并把这种问题总结为过度设计。 这种总结其实有失偏颇, 因为设计模式所提供的灵活性本身就是有代价的。 很多程序员所编写的商业应用程序, 其问题领域其实相对固定,对代码的灵活性和重用性要求并不高(相对于与工具包和框架而言), 所以应用程序的编写者往往在不了解设计模式的情况下,也可以很好地解决一些灵活性需求和扩展性需求。

具体来说使用设计模式的必要性的程度是逐级递增的:应用程序(Application) < 工具包/类库(ToolKit/Library) < 框架( Framework )

状态模式(State Pattern)

  • 设计意图
    • GoF: 允许一个对象在其内部状态发生变化时改变自己的行为, 就好像是更换了一个不同的类一样。
  • GOF举例:
    • 考虑一个 TCPConnection 类, 用于代表一个TCP连接。 TCPConnection 的一个对象可以处于多种状态: Established(已建立连接), Listenning(监听中), Closed(已关闭)。当一个 TCPConnection 对象收到其他类型的对象发来的请求时, 它会根据自己当前所处的状态做出不同的响应。例如, 一个 Open 请求得到的响应取决于连接是否处于 Closed 状态或是 Established 状态。
  • 解决方案

    • 状态模式(State Pattern)描述了TCPConnection 类如何在不同的状态下表现不同的行为。
    • 状态模式的核心思想在于引入一个抽象类 TCPState 来代表 TCPConnection 所处的不同状态。 TCPState 类声明一个对于不同状态通用的接口, TCPState 的子类来实现这些依赖于状态而变化的行为。 例如, TCPEstablished 和 TCPClosed 分别实现TCPConnection 在 Established 状态和 Closed 状态时的行为模式。

    这里写图片描述

  • 图例说明

    • 图片中的空心三角箭头,代表着继承(extends)或实现(Implement)关系, 由继承者/实现者 指向 被继承者/被继承者。
    • 图片中的头部是菱形, 末尾是实心三角的箭头, 代表着一对一的引用关系。
    • 图片中的末端有圆圈的虚线是一个对方法体内容用伪代码说明的关系
  • TCPConnection 维护了一个状态对象(一个 TCPState 子类对象), 这个对象代表了TCPConnection 当前所处的状态。 TCPConnection 类把所有会依赖于状态而变化的行为都委托给状态对象来处理。

  • 每当 一个 TCPConnection 的状态发生改变时, TCPConnection 对象就更改他所使用的状态对象。 例如, 当连接状态从 Established 变化为 Closed 时, TCPConnection 类就用 TCPColosed 对象替换 TCPEstablished 对象。

应用场景

在如下任意一种情形时使用状态模式

  • 一个对象的行为依赖于其内部状态, 并且它必须在运行时刻根据自己的状态改变行为。
  • 多个操作存在大量的, 多个部分的依赖于对象状态的条件语句。 而这个状态一般用一个或多个常量表示。 一些操作包含相同的的条件判断结构。 状态模式会把每一个条件分支放到单独的类中。 这可以使得你把对象的状态也当做一个对象来处理, 使得每个状态的行为可以独立于其他对象变化

状态模式结构

这里写图片描述

  • 图例说明
    • 图片中的空心三角箭头,代表着继承(extends)或实现(Implement)关系, 由继承者/实现者 指向 被继承者/被继承者。
    • 图片中的实心三角箭头且箭头末尾没有圆圈的, 代表着一对一的引用关系。
    • 图片中的末端有圆圈的虚线是一个对方法体内容用伪代码说明的关系

状态模式与 If/Swtich Case 语句

回顾一下状态模式的设计意图“允许一个对象内部状态发生变化时改变自己的行为”, 这完全可以通过条件语句执行不同的逻辑来实现, 为什么要单独提出一个设计模式?

  • 的确,状态模式的一个可替换方案就是用一些数值直接代表对象的内部状态, 然后用 if 语句判断对象所处的状态, 进而使得同一个接口在对象内部状态不同的情况下展现不同的行为。 但这样做会使得大量的 if 语句和 switch case 语句分散到同一个类的不同的方法中。 当我们需要添加一个新的状态时, 需要在不同的代码块中分别去添加相应的条件分支,这显然很不方便。

状态模式的优点

  • 状态模式则将依赖于对象状态的行为归拢了起来, 将不同状态下的行为集中到不同的类中。 这样, 由于所有状态相关的代码都集中在一个 State 对象的子类中,添加新的状态以及相应的行为都会非常容易。
  • 状态模式避免了使用 if/switch 语句时难以扩展和维护的问题, 不同状态的行为被分散到不同的 State 子类中, 这会增加类的数量,与把所有的状态相关行为集中到一个类相比, 代码没有那么紧凑。 但这种分散在对象的状态比较多的时候其实是有益的, 因为如果为不同的状态创建很多的条件分支, 会使得这一部分的代码变得很臃肿, 进而使得代码难以扩展和维护。
  • 状态模式提供了一种更好的方式来组织状态相关的行为。 状态转换的逻辑不必存在于臃肿的 if/switch 块中,可以被划分到不同的 State 子类中。 把每一种状态的转换和该状态下的行为封装到单独的类中,可以使得代码的目的更加清晰。
  • 状态模式使得状态的转换更加明显。 如果一个对象使用一些内部数据值来定义不同的状态, 它的状态转换就体现为一些状态变量的赋值操作。 为不同的状态引入不同的对象可以使得状态的转换更为明显。 而且, 状态对象可以避免 Context 对象处于不一致的状态下, 因为状态转换操作从 Context 的角度来看是原子化的,因为它仅仅需要重新绑定一个状态对象的引用,而不需要为多个变量赋值。

值得思考的问题

  • 在哪里定义状态的转换
    • 状态模式并没有指定应该由哪一个角色(Context 还是 State 子类) 来定义状态切换的条件。 如果状态转换的条件是完全固定的, 那么状态转换的逻辑就可以实现在 Context 类中。 但一般来说, 让 State 的子类来指定他们的后继状态,以及何时进行状态的转换, 是一种较为合适且灵活的方式。
    • 把状态的切换操作分散到多个 State 子类中使得修改或扩展新的 State 子类变得简单 。 这样分散的缺点是, 一个 State 子类必须得了解其后继状态类, 才能完成状态的转换, 这带来了子类之间实现上的依赖。

总结

状态模式提供提供了一种可以替换 大量的 if/switch 语句的组织状态相关的代码行为的方式。 尤其适用于对象有很多状态, 且有对象的大部分行为都会依赖其状态

扫描二维码关注公众号,回复: 3882971 查看本文章

猜你喜欢

转载自blog.csdn.net/lengxiao1993/article/details/73331404
今日推荐