Java 设计模式——状态模式

在阎宏博士的《JAVA 与模式》一书中开头是这样描述状态(State)模式的:

状态模式,又称状态对象模式(Pattern of Objects for States),状态模式是对象的行为模式。

状态模式允许一个对象在其内部状态改变的时候改变其行为。这个对象看上去就像是改变了它的类一样。

状态模式的结构

用一句话来表述,状态模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。状态模式的示意性类图如下所示:
5362a041efca44c2b3c280843f7a7eba.png

状态模式所涉及到的角色有:

  • 环境 (Context) 角色,也成上下文:定义客户端所感兴趣的接口,并且保留一个具体状态类的实例。这个具体状态类的实例给出此环境对象的现有状态。
  • 抽象状态 (State) 角色:定义一个接口,用以封装环境(Context)对象的一个特定的状态所对应的行为。
  • 具体状态 (ConcreteState) 角色:每一个具体状态类都实现了环境(Context)的一个状态所对应的行为。

源代码

环境角色类

5ff26a60c0fc45b28a78e36259e5cf72.png

抽象状态类

38fb67c328e242df8bb637e12e0003d3.png

具体状态类

8a1357aa684d48ab9637c1cf79a14ab5.png

客户端类

6d6dd9fb9294423588d44c529d1a0701.png

从上面可以看出,环境类 Context 的行为 request()是委派给某一个具体状态类的。通过使用多态性原则,可以动态改变环境类 Context 的属性 State 的内容,使其从指向一个具体状态类变换到指向另一个具体状态类,从而使环境类的行为 request() 由不同的具体状态类来执行。

使用场景

考虑一个在线投票系统的应用,要实现控制同一个用户只能投一票,如果一个用户反复投票,而且投票次数超过 5 次,则判定为恶意刷票,要取消该用户投票的资格,当然同时也要取消他所投的票;如果一个用户的投票次数超过 8 次,将进入黑名单,禁止再登录和使用系统。

要使用状态模式实现,首先需要把投票过程的各种状态定义出来,根据以上描述大致分为四种状态:正常投票、反复投票、恶意刷票、进入黑名单。然后创建一个投票管理对象(相当于 Context)。

系统的结构图如下所示:
fa9b1dd983474c09bdda68cf99e9eda4.png

源代码

抽象状态类

a85f823899234029af98a6aa19ca678a.png

具体状态类——正常投票

c6b0a000a3714372ab713bfd93005705.png

具体状态类——重复投票

c9d3ddcf63374558adc335d819e9d057.png

具体状态类——恶意刷票

c5eff615976345eca36b37d6923422a5.png

具体状态类——黑名单

9ac76b10d4b647fab07328c830cf9067.png

环境类

94fc97d855ce44c39a3c8476c80319be.png
281bd72201a44dfc99f8d4f0a01b8f88.png

客户端类

c9d5e258973f4c1b81c48fbe4bbd4546.png

运行结果

d9471f7643ee4889827b13618d4c6e87.png

认识状态模式

  • 状态和行为
    所谓对象的状态,通常指的就是对象实例的属性的值;而行为指的就是对象的功能,再具体点说,行为大多可以对应到方法上。
    状态模式的功能就是分离状态的行为,通过维护状态的变化,来调用不同状态对应的不同功能。也就是说,状态和行为是相关联的,它们的关系可以描述为:状态决定行为
    由于状态是在运行期被改变的,因此行为也会在运行期根据状态的改变而改变。

  • 行为的平行性
    注意平行线而不是平等性。所谓平行性指的是各个状态的行为所处的层次是一样的,相互独立的、没有关联的,是根据不同的状态来决定到底走平行线的哪一条。行为是不同的,当然对应的实现也是不同的,相互之间是不可替换的。
    108905d5ab54438386ca6789ae2cd29d.png
    而平等性强调的是可替换性,大家是同一行为的不同描述或实现,因此在同一个行为发生的时候,可以根据条件挑选任意一个实现来进行相应的处理。
    b9a2c890d7c2436ba3139a86b3c969e3.png

大家可能会发现状态模式的结构和策略模式的结构完全一样,但是,它们的目的、实现、本质却是完全不一样的。还有行为之间的特性也是状态模式和策略模式一个很重要的区别,状态模式的行为是平行性的,不可相互替换的;而策略模式的行为是平等性的,是可以相互替换的。

  • 环境和状态处理对象
    在状态模式中,环境 (Context) 是持有状态的对象,但是环境 (Context) 自身并不处理跟状态相关的行为,而是把处理状态的功能委托给了状态对应的状态处理类来处理。
    在具体的状态处理类中经常需要获取环境 (Context) 自身的数据,甚至在必要的时候会回调环境 (Context) 的方法,因此,通常将环境 (Context) 自身当作一个参数传递给具体的状态处理类。
    客户端一般只和环境 (Context) 交互。客户端可以用状态对象来配置一个环境(Context),一旦配置完毕,就不再需要和状态对象打交道了。客户端通常不负责运行期间状态的维护,也不负责决定后续到底使用哪一个具体的状态处理对象。