18.2 解 决 方 案
18.2.1 使用状态模式来解决问题
用来解决上述问题的一个合理的解决方案就是状态模式。那么什么是状态模式呢?
1.状态模式的定义
允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
2.应用状态模式来解决的思路
仔细分析上面的问题,会发现,那几种用户投票的类型,就相当于是描述了人员的几种投票状态,而各个状态和对应的功能处理具有很强的对应性,有点类似于“一个萝卜一个坑”,各个状态下的处理基本上都是不一样的,也不存在可以相互替换的可能。
为了解决上面提出的问题,很自然的一个设计就是,首先把状态和状态对应的行为从原来的大杂烩代码中分离出来,把每个状态所对应的功能处理封装在一个独立的类里面,这样选择不同处理的时候,其实就是在选择不同的状态处理类。
为了统一操作这些不同的状态类,定义一个状态接口来约束它们,这样外部就可以面向这个统一的状态接口编程,而无须关心具体的状态类实现了。
这样一来,要修改某种投票情况所对应的具体功能处理,只需直接修改或者扩展某个状态处理类的功能就可以了。而要添加新的功能就更简单,直接添加新的状态处理类就可以了,当然在使用Context的时候,需要设置使用这个新的状态类的实例。
18.2.2 状态模式的结构和说明
状态模式的结构如图18.1所示:
图18.1 状态模式的结构示意图
■ Context:环境,也称上下文,通常用来定义客户感兴趣的接口,同时维护一个来具体处理当前状态的实例对象。
■ State:状态接口,用来封装与上下文的一个特定状态所对应的行为。
■ ConcreteState:具体实现状态处理的类,每个类实现一个跟上下文相关的状态的具体处理。
18.2.3 状态模式示例代码
(1)首先来看看状态接口。示例代码如下:
(2)再来看看具体的状态实现。目前具体的实现ConcreteStateA和ConcreteStateB示范的是一样的,只是名称不同。示例代码如下:
(3)接下来看看上下文的具体实现。上下文通常用来定义客户感兴趣的接口,同时维护一个具体的处理当前状态的实例对象。示例代码如下:
18.2.4 使用状态模式重写示例
看完了上面的状态模式的知识,有些朋友跃跃欲试,打算使用状态模式来重写前面的示例。要想使用状态模式,首先需要把投票过程的各种状态定义出来,然后把这些状态对应的处理从原来大杂烩的实现中分离出来,形成独立的状态处理对象。而原来投票管理的对象就相当于Context了。
把状态对应的行为分离出去以后,怎么调用呢?
按照状态模式的示例,是在Context中处理客户请求的时候,转调相应的状态对应的具体的状态处理类来进行处理。
那就引出下一个问题:那么这些状态怎么变化呢?
看原来的实现,就是在投票方法中,根据投票的次数进行判断,并维护投票类型的变化。那好,也依葫芦画瓢,就在投票方法中来维护状态变化。
这个时候的程序结构如图18.2所示。
图18.2 状态模式的示例程序机构示意图
(1)先来看看状态接口的代码实现。示例代码如下:
(2)定义了状态接口,那就该来看看如何实现各个状态对应的处理了。现在的实现很简单,就是把原来的实现从投票管理类中分离出来就可以了。
下面来看看正常投票状态对应的处理。示例代码如下:
下面来看看重复投票状态对应的处理。示例代码如下:
下面来看看恶意投票状态对应的处理。示例代码如下:
下面来看看黑名单状态对应的处理。示例代码如下:
(3)定义好了状态接口并实现了各个状态对应的处理,看看现在的投票管理,相当于状态模式中的上下文,相对而言,它的改变如下。
■ 添加了持有状态处理对象。
■ 添加了能获取记录用户投票结果的Map的方法,各个状态处理对象,在进行状态对应处理的时候,需要获取上下文中的记录用户投票结果的Map数据。
■ 在vote()方法实现中,原来判断投票的类型就变成了判断投票的状态;而原来每种投票类型对应的处理,现在已经封装到对应的状态对象中去了,因此直接转调对应的状态对象的方法即可。
示例代码如下:
(4)该写个客户端来测试一下了,经过这样修改,好用吗?试试看就知道了。客户端没有任何的改变,跟前面实现的一样。示例代码如下:
运行一下试试吧,结果应该是跟前面一样的,也就是说都是实现一样的功能,只是采用了状态模式来实现。测试结果如下:
恭喜你投票成功
请不要重复投票
请不要重复投票
请不要重复投票
你有恶意刷票行为,取消投票资格
你有恶意刷票行为,取消投票资格
你有恶意刷票行为,取消投票资格
进入黑名单,将禁止登录和使用本系统
从上面的示例可以看出,状态的转换基本上都是内部行为,主要在状态模式内部来维护。比如对于投票的人员,任何时候他的操作都是投票,但是投票管理对象的处理却不一定一样,会根据投票的次数来判断状态,然后根据状态去选择不同的处理。