动机

在软件构建过程中,某些对象的状态如果改变,其行为也会随之而发生变化。
比如

  • 文档处于只读状态,其支持的行为和读写状态支持的行为就可能完全不同

如何在运行时根据对象的状态来透明地更改对象的行为?而不会为对象操作和状态转换之间引入紧耦合?

代码:网络的应用

原始代码

状态不同,NetworkProcessor的行为也不同

  1. //网络状态
  2. enum NetworkState
  3. {
  4. Network_Open,
  5. Network_Close,
  6. Network_Connect,
  7. };
  8. //网络处理者
  9. class NetworkProcessor{
  10. NetworkState state;
  11. public:
  12. void Operation1(){
  13. if (state == Network_Open){
  14. //打开时,有这些行为
  15. //**********
  16. state = Network_Close; //打开之后,状态又发生变化
  17. }
  18. else if (state == Network_Close){
  19. //关闭时,有这些行为
  20. //..........
  21. state = Network_Connect;
  22. }
  23. else if (state == Network_Connect){
  24. //连接时,有这些行为
  25. //$$$$$$$$$$
  26. state = Network_Open;
  27. }
  28. }
  29. public void Operation2(){
  30. if (state == Network_Open){
  31. //**********
  32. state = Network_Connect;
  33. }
  34. else if (state == Network_Close){
  35. //.....
  36. state = Network_Open;
  37. }
  38. else if (state == Network_Connect){
  39. //$$$$$$$$$$
  40. state = Network_Close;
  41. }
  42. }
  43. public void Operation3(){
  44. }
  45. };

引入状态模式

未来会不会有其他状态,比如Network_Wait。如果在这一版本的代码上进行更改,是违背开闭原则的。
从状态不同来看,和之前的“策略模式”很类似,在代码中出现很多if-else,就可以使用策略模式来改善。可以看以下代码,思考一下与策略模式有哪些不同。

  1. //把之前的枚举类型,抽象成了一个抽象基类
  2. //并把之前关于状态的操作,封装到对应的状态对象里
  3. class NetworkState{
  4. public:
  5. //下一个状态
  6. NetworkState* pNext;
  7. //把所有与状态有关的操作,全变成某个状态的行为
  8. virtual void Operation1()=0;
  9. virtual void Operation2()=0;
  10. virtual void Operation3()=0;
  11. virtual ~NetworkState(){}
  12. };
  13. //打开状态
  14. class OpenState :public NetworkState{
  15. static NetworkState* m_instance;
  16. public:
  17. //某个状态没有必要有多个,所以可以使用单例模式
  18. static NetworkState* getInstance(){
  19. if (m_instance == nullptr) {
  20. m_instance = new OpenState();
  21. }
  22. return m_instance;
  23. }
  24. //打开业务执行之后,会转换成另一个状态
  25. void Operation1(){
  26. //**********
  27. pNext = CloseState::getInstance();
  28. }
  29. void Operation2(){
  30. //..........
  31. pNext = ConnectState::getInstance();
  32. }
  33. void Operation3(){
  34. //$$$$$$$$$$
  35. pNext = OpenState::getInstance();
  36. }
  37. };
  38. //关闭状态
  39. class CloseState:public NetworkState{ }
  40. //网络处理者
  41. class NetworkProcessor{
  42. //这里不是存枚举了,而是一个状态对象
  43. NetworkState* pState;
  44. public:
  45. NetworkProcessor(NetworkState* pState){
  46. this->pState = pState;
  47. }
  48. void Operation1(){
  49. //...
  50. //虚函数的本质,其实就是运行时的一个if-else
  51. //如果pState是一个OpenState,那执行的是OpenState的Operator1
  52. pState->Operation1();
  53. pState = pState->pNext; //在状态对象中管理,你下一个状态是谁
  54. //...
  55. }
  56. void Operation2(){
  57. //...
  58. pState->Operation2();
  59. pState = pState->pNext;
  60. //...
  61. }
  62. void Operation3(){
  63. //...
  64. pState->Operation3();
  65. pState = pState->pNext;
  66. //...
  67. }
  68. };

综上,其实是把第一个版本中的状态给抽象出来了:一个状态对象,负责管理它自己的状态与这个状态所要执行的操作。
NetworkProcessor中并不涉及具体的状态,而是操作状态的抽象基类!
从而达到一种稳定,如果之后有新的状态过来,添加新的状态即可,不需要更改原先的代码。这就是对扩展开放,对修改关闭。

模式定义

允许一个对象在其内部状态改变时,改变它的行为。从而使对象看起来似乎修改了其行为——《设计模式》GoF。

结构

image.png

要点总结

State模式将所有与一个特定状态相关的行为都放入一个State的子类对象中,在对象状态切换时,切换相应的对象;但同时维持State的接口,这样实现了具体操作与状态转换之间的解耦。

为不同的状态引入不同的对象使得状态转换变得更加明确,而且可以保证不会出现状态不一致的情况,因为转换是原子性的——即要么彻底转换过来,要么不转换。

  • OpenState只关心三个操作之后,我的下一个状态是什么。不需要想其他情况,这样才不会导致状态转换后的bug。如果像第一个版本中,if-else来执行,那对象转换关系是非常复杂的,很容易写出bug

如果State对象没有实例变量,那么各上下文可以共享同一个State对象,从而节省对象开销。

状态模式与策略模式的区别

在使用设计模式的过程中,你慢慢会发现,很多模式它们之间越来越像、越来越像。这是为什么呢?是因为你看问题的角度多了。
你可以尝试,把它们的类图都找出来,仔细观察它们的相同点和不同点,你会发现它们的相同点越来越多,越来越多。你甚至会把它们具体名字都忘了,不会太计较它们之间的差异性,而是关注它们到底解决了什么稳定点、变化点,以及怎么处理稳定点和变化点之间的关系
慢慢的,你会发现自己不再那么生搬硬套了

  • 不再追求去搞清楚这段代码是什么设计模式,这个设计模式是不是这样写的
  • 你会发现它们的差异性,叫什么名字,代码怎么写,这些没有那么重要的了。它们无非是设计上的一种演化而已,松耦合设计原则的演化而已

比如状态模式与策略模式,你甚至都可以把它们看成同一种模式。在你眼里它们是一样的,出现了if-else过多的问题,用枚举来解决,就可以转成用多态的方式来实现,在运行时做if-else,而不用在代码里写if-else。你掌握了这个,真的设计模式叫什么,怎么写,怎么实现,都没有这么重要了。