18.2 解 决 方 案

18.2.1 使用状态模式来解决问题

用来解决上述问题的一个合理的解决方案就是状态模式。那么什么是状态模式呢?

1.状态模式的定义

允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

2.应用状态模式来解决的思路

仔细分析上面的问题,会发现,那几种用户投票的类型,就相当于是描述了人员的几种投票状态,而各个状态和对应的功能处理具有很强的对应性,有点类似于“一个萝卜一个坑”,各个状态下的处理基本上都是不一样的,也不存在可以相互替换的可能。

为了解决上面提出的问题,很自然的一个设计就是,首先把状态和状态对应的行为从原来的大杂烩代码中分离出来,把每个状态所对应的功能处理封装在一个独立的类里面,这样选择不同处理的时候,其实就是在选择不同的状态处理类。

为了统一操作这些不同的状态类,定义一个状态接口来约束它们,这样外部就可以面向这个统一的状态接口编程,而无须关心具体的状态类实现了。

这样一来,要修改某种投票情况所对应的具体功能处理,只需直接修改或者扩展某个状态处理类的功能就可以了。而要添加新的功能就更简单,直接添加新的状态处理类就可以了,当然在使用Context的时候,需要设置使用这个新的状态类的实例。

18.2.2 状态模式的结构和说明

状态模式的结构如图18.1所示:

图片

图18.1 状态模式的结构示意图

■ Context:环境,也称上下文,通常用来定义客户感兴趣的接口,同时维护一个来具体处理当前状态的实例对象。

■ State:状态接口,用来封装与上下文的一个特定状态所对应的行为。

■ ConcreteState:具体实现状态处理的类,每个类实现一个跟上下文相关的状态的具体处理。

18.2.3 状态模式示例代码

(1)首先来看看状态接口。示例代码如下:

9d81eeffc08a46b696756407c498ef28

(2)再来看看具体的状态实现。目前具体的实现ConcreteStateA和ConcreteStateB示范的是一样的,只是名称不同。示例代码如下:

2d70b5b59d4841e590e884d7c538a71b

(3)接下来看看上下文的具体实现。上下文通常用来定义客户感兴趣的接口,同时维护一个具体的处理当前状态的实例对象。示例代码如下:

758c8f1d92704867bad1ccead74e4a2f

5938f2b9033144d49e82b08e5a78c103

18.2.4 使用状态模式重写示例

看完了上面的状态模式的知识,有些朋友跃跃欲试,打算使用状态模式来重写前面的示例。要想使用状态模式,首先需要把投票过程的各种状态定义出来,然后把这些状态对应的处理从原来大杂烩的实现中分离出来,形成独立的状态处理对象。而原来投票管理的对象就相当于Context了。

把状态对应的行为分离出去以后,怎么调用呢?

按照状态模式的示例,是在Context中处理客户请求的时候,转调相应的状态对应的具体的状态处理类来进行处理。

那就引出下一个问题:那么这些状态怎么变化呢?

看原来的实现,就是在投票方法中,根据投票的次数进行判断,并维护投票类型的变化。那好,也依葫芦画瓢,就在投票方法中来维护状态变化。

这个时候的程序结构如图18.2所示。

图片

图18.2 状态模式的示例程序机构示意图

(1)先来看看状态接口的代码实现。示例代码如下:

6a1690410de24a90b435426b470e4d77

(2)定义了状态接口,那就该来看看如何实现各个状态对应的处理了。现在的实现很简单,就是把原来的实现从投票管理类中分离出来就可以了。

下面来看看正常投票状态对应的处理。示例代码如下:

a501ed52ac9f4395a93bbe8a87a410a7

下面来看看重复投票状态对应的处理。示例代码如下:

4d9348af4ad64a4baed75094c5cd92dd

下面来看看恶意投票状态对应的处理。示例代码如下:

1ca4afb829b94b418eee22c40261bb62

a894562314a94c9cbf1c1df9f5e12733

下面来看看黑名单状态对应的处理。示例代码如下:

1b9455b3c11b49d1adfca68ee6df9eb6

(3)定义好了状态接口并实现了各个状态对应的处理,看看现在的投票管理,相当于状态模式中的上下文,相对而言,它的改变如下。

■ 添加了持有状态处理对象。

■ 添加了能获取记录用户投票结果的Map的方法,各个状态处理对象,在进行状态对应处理的时候,需要获取上下文中的记录用户投票结果的Map数据。

■ 在vote()方法实现中,原来判断投票的类型就变成了判断投票的状态;而原来每种投票类型对应的处理,现在已经封装到对应的状态对象中去了,因此直接转调对应的状态对象的方法即可。

示例代码如下:

68d95cfe2c27433c8e33a9d87db04e73

56da9a3d113c4d22a1bd772ab54207fe

9fb92f6a9f6747f99342ca36801e82ac

115b8e8a98ed462da4d375157a2bb76a

(4)该写个客户端来测试一下了,经过这样修改,好用吗?试试看就知道了。客户端没有任何的改变,跟前面实现的一样。示例代码如下:

5dcd8d7c31714e668449adafc010b87a

运行一下试试吧,结果应该是跟前面一样的,也就是说都是实现一样的功能,只是采用了状态模式来实现。测试结果如下:

恭喜你投票成功

请不要重复投票

请不要重复投票

请不要重复投票

你有恶意刷票行为,取消投票资格

你有恶意刷票行为,取消投票资格

你有恶意刷票行为,取消投票资格

进入黑名单,将禁止登录和使用本系统

从上面的示例可以看出,状态的转换基本上都是内部行为,主要在状态模式内部来维护。比如对于投票的人员,任何时候他的操作都是投票,但是投票管理对象的处理却不一定一样,会根据投票的次数来判断状态,然后根据状态去选择不同的处理。