一天一模式之17状态模式

本节课程概览

学习状态模式

  • 一:初识状态模式
    包括:定义、结构、参考实现
  • 二:体会状态模式
    包括:场景问题、不用模式的解决方案、使用模式的解决方案
  • 三:理解状态模式
    包括:认识状态模式、状态的维护和转换控制 、使用数据库来维护状态 、
    模拟工作流 、状态模式的优缺点

  • 四:思考状态模式
    包括:状态模式的本质、何时选用

初识状态模式

定义

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

结构和说明


- Context:环境,也称上下文,通常用来定义客户感兴趣的接口,同时维护一个来具
体处理当前状态的实例对象。
- State:状态接口,用来封装与上下文的一个特定状态所对应的行为。

- ConcreteState:具体实现状态处理的类,每个类实现一个状态的具体处理。

示例

封装与Context的一个特定状态相关的行为
package cn.javass.dp.state.example2;


/**
 * 封装与Context的一个特定状态相关的行为
 */
public interface State {
    /**
     * 状态对应的处理
     * @param sampleParameter 示例参数,说明可以传入参数,具体传入
     *             什么样的参数,传入几个参数,由具体应用来具体分析
     */
    public void handle(String sampleParameter);
}
实现一个与Context的一个特定状态相关的行为
package cn.javass.dp.state.example2;


/**
 * 实现一个与Context的一个特定状态相关的行为
 */
public class ConcreteStateA implements State {
    public void handle(String sampleParameter) {
        //实现具体的处理
    }
}
实现一个与Context的一个特定状态相关的行为
package cn.javass.dp.state.example2;


/**
 * 实现一个与Context的一个特定状态相关的行为
 */
public class ConcreteStateB implements State {
    public void handle(String sampleParameter) {
        //实现具体的处理
    }
}
定义客户感兴趣的接口,通常会维护一个State类型的对象实例
package cn.javass.dp.state.example2;


/**
 * 定义客户感兴趣的接口,通常会维护一个State类型的对象实例
 */
public class Context {
    /**
     * 持有一个State类型的对象实例
     */
    private State state;
    /**
     * 设置实现State的对象的实例 
     * @param state 实现State的对象的实例 
     */
    public void setState(State state) {
        this.state = state;
    }
    /**
     * 用户感兴趣的接口方法
     * @param sampleParameter 示意参数
     */
    public void request(String sampleParameter) {
        //在处理中,会转调state来处理
        state.handle(sampleParameter);
    }


}

体会状态模式

实现在线投票

考虑一个在线投票的应用,要实现控制同一个用户只能投一票,如果一个

用户反复投票,而且投票次数超过5次,则判定为恶意刷票,要取消该用户投票
的资格,当然同时也要取消他所投的票。如果一个用户的投票次数超过8次,将
进入黑名单,禁止再登录和使用系统。
该怎么实现这样的功能呢?

不用模式的解决方案

示例

投票管理
package cn.javass.dp.state.example1;
import java.util.*;
/**
 * 投票管理
 */
public class VoteManager {
    /**
     * 记录用户投票的结果,Map<String,String>对应Map<用户名称,投票的选项>
     */
    private Map<String,String> mapVote = new HashMap<String,String>();
    /**
     * 记录用户投票次数,Map<String,Integer>对应Map<用户名称,投票的次数>
     */
    private Map<String,Integer> mapVoteCount = new HashMap<String,Integer>();
    /**
     * 投票
     * @param user 投票人,为了简单,就是用户名称
     * @param voteItem 投票的选项
     */
    public void vote(String user,String voteItem){
        //1:先为该用户增加投票的次数
        //先从记录中取出已有的投票次数
        Integer oldVoteCount = mapVoteCount.get(user);
        if(oldVoteCount==null){
            oldVoteCount = 0;
        }
        oldVoteCount = oldVoteCount + 1;
        mapVoteCount.put(user, oldVoteCount);

        //2:判断该用户投票的类型,到底是正常投票、重复投票、恶意投票还是上黑名单
        //然后根据投票类型来进行相应的操作  
        if(oldVoteCount==1){
            //正常投票
            //记录到投票记录中
            mapVote.put(user, voteItem);
            System.out.println("恭喜你投票成功");
        }else if(oldVoteCount>1 && oldVoteCount<5){
            //重复投票
            //暂时不做处理
            System.out.println("请不要重复投票");
        }else if(oldVoteCount >= 5 && oldVoteCount<8){
            //恶意投票
            //取消用户的投票资格,并取消投票记录
            String s = mapVote.get(user);
            if(s!=null){
                mapVote.remove(user);
            }
            System.out.println("你有恶意刷票行为,取消投票资格");
        }else if(oldVoteCount>=8){
            //黑名单
            //记入黑名单中,禁止登录系统了
            System.out.println("进入黑名单,将禁止登录和使用本系统");
        }
    }
}
客户端
package cn.javass.dp.state.example1;

public class Client {
    public static void main(String[] args) {
        VoteManager vm = new VoteManager();
        for(int i=0;i<8;i++){
            vm.vote("u1", "A");
        }
    }
}
有何问题

看起来很简单,是不是?但是你想想,在vote()方法中那么多判断,还有

每个判断对应的功能处理都放在一起,是不是有点太杂乱了,那简直就是个大杂
烩,如果把每个功能都完整的实现出来,那vote()方法会很长的。

一个问题是:如果现在要修改某种投票情况所对应的具体功能处理,那就
需要在那个大杂烩里面,找到相应的代码块,然后进行改动。

另外一个问题是:如果要添加新的功能,比如投票超过8次但不足10次的,
给个机会,只是禁止登录和使用系统3天,如果再犯,才永久封掉账号,该怎么
办呢?那就需要改动投票管理的源代码,在上面的if-else结构中再添加一个
else if块进行处理。

该如何实现才能做到:既能够很容易的给vote()方法添加新的功能,又能够
很方便的修改已有的功能处理呢?

使用模式来解决的思路

仔细分析上面的问题,会发现,那几种用户投票的类型,就相当于是描述

了人员的几种投票状态,而各个状态和对应的功能处理具有很强的对应性,有点
类似于“一个萝卜一个坑”,各个状态下的处理基本上都是不一样的,也不存在
可以相互替换的可能。

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

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

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

使用模式的解决方案的类图

示例

封装一个投票状态相关的行为
package cn.javass.dp.state.example3;
/**
 * 封装一个投票状态相关的行为
 */
public interface VoteState {
    /**
     * 处理状态对应的行为
     * @param user 投票人
     * @param voteItem 投票项
     * @param voteManager 投票上下文,用来在实现状态对应的功能处理的时候,
     *                    可以回调上下文的数据
     */
    public void vote(String user,String voteItem,VoteManager voteManager);
}
正常投票
package cn.javass.dp.state.example3;

public class NormalVoteState implements VoteState{
    public void vote(String user, String voteItem, VoteManager voteManager) {
        //正常投票
        //记录到投票记录中
        voteManager.getMapVote().put(user, voteItem);
        System.out.println("恭喜你投票成功");
    }
}
重复投票
package cn.javass.dp.state.example3;

public class RepeatVoteState implements VoteState{
    public void vote(String user, String voteItem, VoteManager voteManager) {
        //重复投票
        //暂时不做处理
        System.out.println("请不要重复投票");
    }
}
恶意投票
package cn.javass.dp.state.example3;

public class SpiteVoteState implements VoteState{
    public void vote(String user, String voteItem, VoteManager voteManager) {
        //恶意投票
        //取消用户的投票资格,并取消投票记录
        String s = voteManager.getMapVote().get(user);
        if(s!=null){
            voteManager.getMapVote().remove(user);
        }
        System.out.println("你有恶意刷票行为,取消投票资格");
    }
}
黑名单
package cn.javass.dp.state.example3;

public class BlackVoteState implements VoteState{
    public void vote(String user, String voteItem, VoteManager voteManager) {
        //黑名单
        //记入黑名单中,禁止登录系统了
        System.out.println("进入黑名单,将禁止登录和使用本系统");
    }
}
投票管理
package cn.javass.dp.state.example3;
import java.util.*;
/**
 * 投票管理
 */
public class VoteManager {
    /**
     * 持有状态处理对象
     */
    private VoteState state = null;
    /**
     * 记录用户投票的结果,Map<String,String>对应Map<用户名称,投票的选项>
     */
    private Map<String,String> mapVote = new HashMap<String,String>();
    /**
     * 记录用户投票次数,Map<String,Integer>对应Map<用户名称,投票的次数>
     */
    private Map<String,Integer> mapVoteCount = new HashMap<String,Integer>();

    /**
     * 获取记录用户投票结果的Map
     * @return 记录用户投票结果的Map
     */
    public Map<String, String> getMapVote() {
        return mapVote;
    }

    /**
     * 投票
     * @param user 投票人,为了简单,就是用户名称
     * @param voteItem 投票的选项
     */
    public void vote(String user,String voteItem){
        //1:先为该用户增加投票的次数
        //先从记录中取出已有的投票次数
        Integer oldVoteCount = mapVoteCount.get(user);
        if(oldVoteCount==null){
            oldVoteCount = 0;
        }
        oldVoteCount = oldVoteCount + 1;
        mapVoteCount.put(user, oldVoteCount);

        //2:判断该用户投票的类型,就相当于是判断对应的状态
        //到底是正常投票、重复投票、恶意投票还是上黑名单的状态
        if(oldVoteCount==1){
            state = new NormalVoteState2();
        }else if(oldVoteCount>1 && oldVoteCount<5){
            state = new RepeatVoteState();
        }else if(oldVoteCount >= 5 && oldVoteCount<8){
            state = new SpiteVoteState();
        }else if(oldVoteCount>=8 && oldVoteCount<=10){
            state = new BlackWarnVoteState();
        }else if(oldVoteCount>=10){
            state = new BlackVoteState();
        }
        //然后转调状态对象来进行相应的操作
        state.vote(user, voteItem, this);
    }
}
客户端
package cn.javass.dp.state.example3;

public class Client {
    public static void main(String[] args) {
        VoteManager vm = new VoteManager();
        for(int i=0;i<11;i++){
            vm.vote("u1", "A");
        }
    }
}
修改功能
修改普通投票状态,添加积分奖励
package cn.javass.dp.state.example3;

public class NormalVoteState2 extends NormalVoteState{
    public void vote(String user, String voteItem, VoteManager voteManager) {
        //先调用已有的功能
        super.vote(user, voteItem, voteManager);
        //给予积分奖励,示意一下
        System.out.println("奖励积分10分");
    }
}
扩展功能
扩展黑名单警告状态
package cn.javass.dp.state.example3;

public class BlackWarnVoteState implements VoteState{
    public void vote(String user, String voteItem, VoteManager voteManager) {
        //待进黑名单警告状态
        System.out.println("禁止登录和使用系统3天");
    }
}
投票管理

修改,和扩展功能需要修改上下文代码,并不是很友好

package cn.javass.dp.state.example3;
import java.util.*;
/**
 * 投票管理
 */
public class VoteManager2 {
    /**
     * 持有状态处理对象
     */
    private VoteState state = null;
    /**
     * 记录用户投票的结果,Map<String,String>对应Map<用户名称,投票的选项>
     */
    private Map<String,String> mapVote = new HashMap<String,String>();
    /**
     * 记录用户投票次数,Map<String,Integer>对应Map<用户名称,投票的次数>
     */
    private Map<String,Integer> mapVoteCount = new HashMap<String,Integer>();

    /**
     * 获取记录用户投票结果的Map
     * @return 记录用户投票结果的Map
     */
    public Map<String, String> getMapVote() {
        return mapVote;
    }

    /**
     * 投票
     * @param user 投票人,为了简单,就是用户名称
     * @param voteItem 投票的选项
     */
    public void vote(String user,String voteItem){
        //1:先为该用户增加投票的次数
        //先从记录中取出已有的投票次数
        Integer oldVoteCount = mapVoteCount.get(user);
        if(oldVoteCount==null){
            oldVoteCount = 0;
        }
        oldVoteCount = oldVoteCount + 1;
        mapVoteCount.put(user, oldVoteCount);

        //2:判断该用户投票的类型,就相当于是判断对应的状态
        //到底是正常投票、重复投票、恶意投票还是上黑名单的状态
        if(oldVoteCount==1){
            state = new NormalVoteState();
        }else if(oldVoteCount>1 && oldVoteCount<5){
            state = new RepeatVoteState();
        }else if(oldVoteCount >= 5 && oldVoteCount<8){
            state = new SpiteVoteState();
        }else if(oldVoteCount>=8 && oldVoteCount<10){
            state = new BlackWarnVoteState();
        }else if(oldVoteCount>10){
            state = new BlackVoteState();
        }
        //然后转调状态对象来进行相应的操作
//      state.vote(user, voteItem, this);
    }
}

理解状态模式

认识状态模式

1:状态和行为

所谓对象的状态,通常指的就是对象实例的属性的值;而行为指的就是对
象的功能,再具体点说,行为多半可以对应到方法上。

状态模式的功能就是分离状态的行为,通过维护状态的变化,来调用不同
的状态对应的不同的功能。

也就是说,状态和行为是相关联的,它们的关系可描述为:状态决定行为
由于状态是在运行期被改变的,因此行为也会在运行期,根据状态的改变
而改变,看起来,同一个对象,在不同的运行时刻,行为是不一样的,就像是类
被修改了一样

2:行为的平行性

注意是平行性而不是平等性。所谓平行性指的是各个状态的行为所处的层
次是一样的,相互是独立的、没有关联的,是根据不同的状态来决定到底走平行
线的那一条,行为是不同的,当然对应的实现也是不同的,相互之间不可替换。

而平等性强调的是可替换性,大家是同一行为的不同描述或实现,因此在
同一个行为发生的时候,可以根据条件来挑选任意一个实现来进行相应的处理。

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

3:上下文和状态处理对象

在状态模式中,上下文是持有状态的对象,但是上下文自身并不处理跟状
态相关的行为,而是把处理状态的功能委托给了状态对应的状态处理类来处理。

在具体的状态处理类里面经常需要获取上下文自身的数据,甚至在必要的
时候会回调上下文的方法,因此,通常将上下文自身当作一个参数传递给具体的
状态处理类。

客户端一般只和上下文交互,客户端可以用状态对象来配置一个上下文,
一旦配置完毕,就不再需要和状态对象打交道了,客户端通常不负责运行期间状
态的维护,也不负责决定到底后续使用哪一个具体的状态处理对象。

4:不完美的OCP体验

使用状态模式来修改和扩展功能,是没有完全遵循OCP原则 的。由于状态
的维护和转换在状态模式结构里面,不管你是扩展了状态实现类,还是新添加了
状态实现类,都需要修改状态维护和转换的地方,以使用新的实现。

5:创建和销毁状态对象

在应用状态模式的时候,有一个常见的考虑,那就是:究竟何时创建和销
毁状态对象。常见的有几个选择:
- 1:一个是当需要使用状态对象的时候创建,使用完后就销毁它们
- 2:另一个是提前创建它们并且始终不销毁
- 3:还有一种是采用延迟加载和缓存合用的方式,就是当第一次需要使用状态对象的
时候创建,使用完后并不销毁对象,而是把这个对象缓存起来,等待下一次使
用,而且在合适的时候,会由缓存框架销毁状态对象

怎么选择呢?下面给出选择建议:

  • 1:如果要进入的状态在运行时是不可知的,而且上下文是比较稳定的,不会经常改
    变状态,而且使用也不频繁,这个时候建议选第一种方案。
  • 2:如果状态改变很频繁,也就是需要频繁的创建状态对象,而且状态对象还存储着
    大量的信息数据,这种情况建议选第二种方案。
  • 3:如果无法确定状态改变是否频繁,而且有些状态对象的状态数据量大,有些比较
    小,一切都是未知的,建议选第三种方案。

事实上,在实际工程开发中,第三种方案是首选,因为它兼顾了前面两种
方案的优点,而又避免了它们的缺点,几乎能适应各种情况的需要。只是这个方
案在实现的时候,要实现一个合理的缓存框架,而且要考虑多线程并发的问题,
因为需要由缓存框架来在合适的时候销毁状态对象,因此实现上难度稍高点。另
外在实现中还可以考虑结合享元模式,通过享元模式来共享状态对象。

状态模式调用顺序示意图

前面的示例中,采用的是在Context中进行状态的维护和转换,这里就先画
出这种方式的调用顺序示意图

状态的维护和转换控制

所谓状态的维护,指的就是维护状态的数据,就是给状态设置不同的状态
值;而状态的转换,指的就是根据状态的变化来选择不同的状态处理对象。在状
态模式中,通常有两个地方可以进行状态的维护和转换控制。

一个就是在上下文当中,因为状态本身通常被实现为上下文对象的状态,
因此可以在上下文里面进行状态维护,当然也就可以控制状态的转换了。前面投
票的示例就是采用的这种方式。

另外一个地方就是在状态的处理类里面,当每个状态处理对象处理完自身
状态所对应的功能后,可以根据需要指定后继的状态,以便让应用能正确处理后
续的请求。

示例

在状态类中指定和维护下一个状态类,可以很友好修改和扩展

封装一个投票状态相关的行为
package cn.javass.dp.state.example4;
/**
 * 封装一个投票状态相关的行为
 */
public interface VoteState {
    /**
     * 处理状态对应的行为
     * @param user 投票人
     * @param voteItem 投票项
     * @param voteManager 投票上下文,用来在实现状态对应的功能处理的时候,
     *                    可以回调上下文的数据
     */
    public void vote(String user,String voteItem,VoteManager voteManager);
}
正常投票
package cn.javass.dp.state.example4;

public class NormalVoteState implements VoteState{
    public void vote(String user, String voteItem, VoteManager voteManager) {
        //正常投票
        //记录到投票记录中
        voteManager.getMapVote().put(user, voteItem);
        System.out.println("恭喜你投票成功");

        //正常投票完成,维护下一个状态,同一个人再投票就重复了
        voteManager.getMapState().put(user, new RepeatVoteState());
    }
}
重复投票
package cn.javass.dp.state.example4;

public class RepeatVoteState implements VoteState{
    public void vote(String user, String voteItem, VoteManager voteManager) {
        //重复投票
        //暂时不做处理
        System.out.println("请不要重复投票");

        //重复投票完成,维护下一个状态,重复投票到5次,就算恶意投票了
        //注意这里是判断大于等于4,因为这里设置的是下一个状态
        //下一个操作次数就是5了,就应该算是恶意投票了
        if(voteManager.getMapVoteCount().get(user) >= 4){
            voteManager.getMapState().put(user, new SpiteVoteState());
        }
    }
}
恶意投票
package cn.javass.dp.state.example4;

public class SpiteVoteState implements VoteState{
    public void vote(String user, String voteItem, VoteManager voteManager) {
        //恶意投票
        //取消用户的投票资格,并取消投票记录
        String s = voteManager.getMapVote().get(user);
        if(s!=null){
            voteManager.getMapVote().remove(user);
        }
        System.out.println("你有恶意刷票行为,取消投票资格");

        //恶意投票完成,维护下一个状态,投票到8次,就进黑名单了
        //注意这里是判断大于等于7,因为这里设置的是下一个状态
        //下一个操作次数就是8了,就应该算是进黑名单了
        if(voteManager.getMapVoteCount().get(user) >= 7){
            voteManager.getMapState().put(user, new BlackWarnVoteState());
        }
    }
}
黑名单
package cn.javass.dp.state.example4;

public class BlackVoteState implements VoteState{
    public void vote(String user, String voteItem, VoteManager voteManager) {
        //黑名单
        //记入黑名单中,禁止登录系统了
        System.out.println("进入黑名单,将禁止登录和使用本系统");

    }
}
投票管理
package cn.javass.dp.state.example4;
import java.util.*;
/**
 * 投票管理
 */
public class VoteManager {
    /**
     * 记录当前每个用户对应的状态处理对象,每个用户当前的状态是不同的
     * Map<String,VoteState>对应Map<用户名称,当前对应的状态处理对象>
     */
    private Map<String,VoteState> mapState = new HashMap<String,VoteState>();

    /**
     * 记录用户投票的结果,Map<String,String>对应Map<用户名称,投票的选项>
     */
    private Map<String,String> mapVote = new HashMap<String,String>();
    /**
     * 记录用户投票次数,Map<String,Integer>对应Map<用户名称,投票的次数>
     */
    private Map<String,Integer> mapVoteCount = new HashMap<String,Integer>();


    /**
     * 获取记录用户投票结果的Map
     * @return 记录用户投票结果的Map
     */
    public Map<String, String> getMapVote() {
        return mapVote;
    }
    /**
     * 获取记录每个用户对应的状态处理对象的Map
     * @return 记录每个用户对应的状态处理对象的Map
     */
    public Map<String, VoteState> getMapState() {
        return mapState;
    }
    /**
     * 获取记录每个用户对应的投票次数的Map
     * @return 记录每个用户对应的投票次数的Map
     */
    public Map<String, Integer> getMapVoteCount() {
        return mapVoteCount;
    }
    /**
     * 投票
     * @param user 投票人,为了简单,就是用户名称
     * @param voteItem 投票的选项
     */
    public void vote(String user,String voteItem){
        //1:先为该用户增加投票的次数
        //先从记录中取出已有的投票次数
        Integer oldVoteCount = mapVoteCount.get(user);
        if(oldVoteCount==null){
            oldVoteCount = 0;
        }
        oldVoteCount = oldVoteCount + 1;
        mapVoteCount.put(user, oldVoteCount);

        //2:获取该用户的投票状态
        VoteState state = mapState.get(user);
        //如果没有投票状态,说明还没有投过票,就初始化一个正常投票状态
        if(state==null){
            state = new NormalVoteState2();
        }

        //然后转调状态对象来进行相应的操作
        state.vote(user, voteItem, this);
    }
}
客户端
package cn.javass.dp.state.example4;

public class Client {
    public static void main(String[] args) {
        VoteManager vm = new VoteManager();
        for(int i=0;i<10;i++){
            vm.vote("u1", "A");
        }
    }
}
修改功能
修改普通投票,添加积分奖励
package cn.javass.dp.state.example4;

public class NormalVoteState2 extends NormalVoteState{
    public void vote(String user, String voteItem, VoteManager voteManager) {
        //先调用已有的功能
        super.vote(user, voteItem, voteManager);
        //给予积分奖励,示意一下
        System.out.println("奖励积分10分");
    }
}
扩展状态
黑名单警告状态
package cn.javass.dp.state.example4;

public class BlackWarnVoteState implements VoteState{
    public void vote(String user, String voteItem, VoteManager voteManager) {
        //待进黑名单警告状态
        System.out.println("禁止登录和使用系统3天");

        //待进黑名单警告状态处理完成,维护下一个状态,投票到10次,就进黑名单了
        //注意这里是判断大于等于9,因为这里设置的是下一个状态
        //下一个操作次数就是10了,就应该算是进黑名单了
        if(voteManager.getMapVoteCount().get(user) >= 9){
            voteManager.getMapState().put(user, new BlackVoteState());
        }
    }
}

那么到底如何选择这两种方式呢?

  • 1:一般情况下,如果状态转换的规则是一定的,一般不需要进行什么扩展规则,那
    么就适合在上下文中统一进行状态的维护。
  • 2:如果状态的转换取决于前一个状态动态处理的结果,或者是依赖于外部数据,为
    了增强灵活性,这种情况下,一般是在状态处理类里面进行状态的维护。
采用让状态对象来维护和转换状态的调用顺序示意图如图

使用数据库来维护状态

在实际开发中,还有一个方式来维护状态,那就是使用数据库,在数据库
中存储下一个状态的识别数据,也就是说,维护下一个状态,演化成了维护下一
个状态的识别数据,比如状态编码。

这样在程序中,通过查询数据库中的数据来得到状态编码,然后再根据状
态编码来创建出相应的状态对象,然后再委托相应的状态对象进行功能处理。

示例

在这里没有真实的连接数据库,只是给出实现思路

重复投票,直接把下一个状态的编码记录入数据库就好了
package cn.javass.dp.state.example5;

public class RepeatVoteState implements VoteState{
    public void vote(String user, String voteItem, VoteManager voteManager) {
        //重复投票
        //暂时不做处理
        System.out.println("请不要重复投票");

        //重复投票完成,维护下一个状态,重复投票到5次,就算恶意投票了
        //注意这里是判断大于等于4,因为这里设置的是下一个状态
        //下一个操作次数就是5了,就应该算是恶意投票了
        if(voteManager.getMapVoteCount().get(user) >= 4){
            //voteManager.getMapState().put(user, new SpiteVoteState());

            //维护状态数据
            //直接把下一个状态的编码记录入数据库就好了

            //把转移记录到数据库
        }
    }
}
投票管理
package cn.javass.dp.state.example5;
import java.util.*;
/**
 * 投票管理
 */
public class VoteManager {
    /**
     * 记录当前每个用户对应的状态处理对象,每个用户当前的状态是不同的
     * Map<String,VoteState>对应Map<用户名称,当前对应的状态处理对象>
     */
    private Map<String,VoteState> mapState = new HashMap<String,VoteState>();
    /**
     * 获取记录每个用户对应的状态处理对象的Map
     * @return 记录每个用户对应的状态处理对象的Map
     */
    public Map<String, VoteState> getMapState() {
        return mapState;
    }
    /**
     * 记录用户投票的结果,Map<String,String>对应Map<用户名称,投票的选项>
     */
    private Map<String,String> mapVote = new HashMap<String,String>();
    /**
     * 记录用户投票次数,Map<String,Integer>对应Map<用户名称,投票的次数>
     */
    private Map<String,Integer> mapVoteCount = new HashMap<String,Integer>();

    /**
     * 获取记录用户投票结果的Map
     * @return 记录用户投票结果的Map
     */
    public Map<String, String> getMapVote() {
        return mapVote;
    }

    /**
     * 获取记录每个用户对应的投票次数的Map
     * @return 记录每个用户对应的投票次数的Map
     */
    public Map<String, Integer> getMapVoteCount() {
        return mapVoteCount;
    }
    /**
     * 投票
     * @param user 投票人,为了简单,就是用户名称
     * @param voteItem 投票的选项
     */
    public void vote(String user,String voteItem)throws Exception{
        //1:先为该用户增加投票的次数
        //先从记录中取出已有的投票次数
        Integer oldVoteCount = mapVoteCount.get(user);
        if(oldVoteCount==null){
            oldVoteCount = 0;
        }
        oldVoteCount = oldVoteCount + 1;
        mapVoteCount.put(user, oldVoteCount);

        VoteState state = null;
        //2:直接从数据库获取该用户对应的下一个状态的状态编码
        String stateId = "从数据库中获取这个值";
        //开始根据状态编码来创建需用的状态对象

        //根据状态编码去获取相应的类
        String className = "根据状态编码去获取相应的类";
        //使用反射创建对象实例,简单示意一下
        Class c = Class.forName(className);
        state = (VoteState)c.newInstance();

//      if("正常投票".equals(stateId)){
//          state = new NormalVoteState();
//      }else if("重复投票".equals(stateId)){
//          state = new RepeatVoteState(); 
//      }else if("恶意投票".equals(stateId)){
//          state = new SpiteVoteState(); 
//      }else if("黑名单".equals(stateId)){
//          state = new BlackVoteState(); 
//      }
        //然后转调状态对象来进行相应的操作
        state.vote(user, voteItem, this);
    }
}

直接把“转移”记录到数据库中

还有一种情况是直接把“转移”记录到数据库中,这样会更灵活。所谓转
移,指的就是描述从A状态到B状态的这么一个转换变化。

比如:在正常投票状态处理对象里面指定使用“转移A”,而“转移A”描
述的就是从正常投票状态转换成重复投票状态。这样一来,假如今后想要让正常
投票处理过后变换成恶意投票状态,那么就不需要修改程序,直接修改数据库中
的数据,把数据库中“转移A”的描述数据修改一下,使其描述从正常投票状态
转换成恶意投票状态就可以了。

模拟工作流

做企业应用的朋友,大多数都接触过工作流,至少处理过业务流程。当然
对于工作流,复杂的应用可能会使用工作流中间件,用工作流引擎来负责流程处
理,这个会比较复杂,其实工作流引擎的实现也可以应用上状态模式,这里不去
讨论。

简单点的,把流程数据存放在数据库里面,然后在程序里面自己来进行流
程控制。对于简单点的业务流程控制,可以使用状态模式来辅助进行流程控制,
因为大部分这种流程都是状态驱动的。

举个例子来说明吧,举个最常见的“请假流程”,流程是这样的:当某人
提出请假申请过后,先由项目经理来审批,如果项目经理不同意,审批就直接结
束;如果项目经理同意了,再看请假的天数是否超过3天,项目经理的审批权限
只有3天以内,如果请假天数在3天以内,那么审批也直接结束,否则就提交给部
门经理;部门经理审核过后,无论是否同意,审批都直接结束。

流程图如图

实现思路

仔细分析上面的流程图和运行过程,把请假单在流程中的各个阶段的状态
分析出来,会发现,整个流程完全可以看成是状态驱动的。
在上面的流程中,请假单大致有如下状态:等待项目经理审核、等待部门
经理审核、审核结束。

既然可以把流程看成是状态驱动的,那么就可以自然的使用上状态模式,
每次当相应的工作人员完成工作,请求流程响应的时候,流程处理的对象会根据
当前所处的状态,把流程处理委托给相应的状态对象去处理。

又考虑到在一个系统中会有很多流程,虽然不像通用工作流那么复杂的设
计,但还是稍稍提炼一下,至少把各个不同的业务流程,在应用状态模式时的公
共功能,或者是架子给搭出来,以便复用这些功能

示例
公共状态接口
package cn.javass.dp.state.example7;
/**
 * 公共状态接口
 */
public interface State {
    /**
     * 执行状态对应的功能处理
     * @param ctx 上下文的实例对象
     */
    public void doWork(StateMachine ctx);
}
请假状态接口
package cn.javass.dp.state.example7;

public interface LeaveRequestState extends State{
    //这里可以扩展跟自己流程相关的处理
}
公共状态处理机,相当于状态模式的Context
package cn.javass.dp.state.example7;
/**
 * 公共状态处理机,相当于状态模式的Context
 * 包含所有流程使用状态模式时的公共功能
 */
public  class StateMachine {
    /**
     * 持有一个状态对象
     */
    private State state = null;
    /**
     * 包含流程处理需要的业务数据对象,不知道具体类型,
     * 用Object,反正只是传递到具体的状态对象里面
     */
    private Object businessVO = null;
    /**
     * 执行工作,客户端处理流程的接口方法。
     * 在客户完成自己的业务工作后调用
     */
    public void doWork(){
        //转调相应的状态对象真正完成功能处理
        this.state.doWork(this);
    }

    public State getState() {
        return state;
    }
    public void setState(State state) {
        this.state = state;
    }
    public Object getBusinessVO() {
        return businessVO;
    }
    public void setBusinessVO(Object businessVO) {
        this.businessVO = businessVO;
    }
}
请假状态处理机
package cn.javass.dp.state.example7;

public class LeaveRequestContext extends StateMachine{
    //这里可以扩展跟自己流程相关的处理
}
请假单对象
package cn.javass.dp.state.example7;
/**
 * 请假单对象
 */
public class LeaveRequestModel {
    /**
     * 请假人
     */
    private String user;
    /**
     * 请假开始时间
     */
    private String beginDate;
    /**
     * 请假天数
     */
    private int leaveDays;
    /**
     * 审核结果
     */
    private String result;

    public String getResult() {
        return result;
    }

    public void setResult(String result) {
        this.result = result;
    }

    public String getUser() {
        return user;
    }

    public String getBeginDate() {
        return beginDate;
    }

    public int getLeaveDays() {
        return leaveDays;
    }
    public void setUser(String user) {
        this.user = user;
    }

    public void setBeginDate(String beginDate) {
        this.beginDate = beginDate;
    }

    public void setLeaveDays(int leaveDays) {
        this.leaveDays = leaveDays;
    }   
}
处理项目经理的审核,处理后可能对应部门经理审核、审核结束之中的一种
package cn.javass.dp.state.example7;

/**
 * 处理项目经理的审核,处理后可能对应部门经理审核、审核结束之中的一种
 */
public class ProjectManagerState implements LeaveRequestState{  
    public void doWork(StateMachine request) {
        //先把业务对象造型回来
        LeaveRequestModel lrm = (LeaveRequestModel)request.getBusinessVO();

        //业务处理,把审核结果保存到数据库中



        //根据选择的结果和条件来设置下一步
        if("同意".equals(lrm.getResult())){
            if(lrm.getLeaveDays() > 3){
                //如果请假天数大于3天,而且项目经理同意了,就提交给部门经理
                request.setState(new DepManagerState());

                //为部门经理增加一个工作               
            }else{  
                //3天以内的请假,由项目经理做主,就不用提交给部门经理了,转向审核结束状态
                request.setState(new  AuditOverState());

                //给申请人增加一个工作,让他察看审核结果
            }           
        }else{
            //项目经理要是不同意的话,也就不用提交给部门经理了,转向审核结束状态
            request.setState(new  AuditOverState());

            //给申请人增加一个工作,让他察看审核结果
        }
    }   
}
处理部门经理的审核,处理后对应审核结束状态
package cn.javass.dp.state.example7;

/**
 * 处理部门经理的审核,处理后对应审核结束状态
 */
public class DepManagerState implements LeaveRequestState{
    public void doWork(StateMachine request) {
        //先把业务对象造型回来
        LeaveRequestModel lrm = (LeaveRequestModel)request.getBusinessVO();

        //业务处理,把审核结果保存到数据库中

        //部门经理审核过后,直接转向审核结束状态了
        request.setState(new AuditOverState());

        //给申请人增加一个工作,让他察看审核结果
    }
}
处理审核结束的类
package cn.javass.dp.state.example7;

import java.util.Scanner;
/**
 * 处理审核结束的类
 */
public class AuditOverState implements LeaveRequestState{
    public void doWork(StateMachine request) {
        //先把业务对象造型回来
        LeaveRequestModel lrm = (LeaveRequestModel)request.getBusinessVO();

        //业务处理,在数据里面记录整个流程结束        
    }
}
使用java控制台模拟连接数据库处理
处理项目经理的审核,处理后可能对应部门经理审核、审核结束之中的一种
package cn.javass.dp.state.example8;

import java.util.Scanner;
/**
 * 处理项目经理的审核,处理后可能对应部门经理审核、审核结束之中的一种
 */
public class ProjectManagerState implements LeaveRequestState{
    public void doWork(StateMachine request) {
        //先把业务对象造型回来
        LeaveRequestModel lrm = (LeaveRequestModel)request.getBusinessVO();
        //实现业务


        System.out.println("项目经理审核中,请稍候......");

        //模拟用户处理界面,通过控制台来读取数据
        System.out.println(lrm.getUser()+"申请从"+lrm.getBeginDate()+
        "开始请假"+lrm.getLeaveDays()+"天,请项目经理审核(1为同意,2为不同意):");
        //读取从控制台输入的数据
        Scanner scanner = new Scanner(System.in);
        if(scanner.hasNext()){
            int a = scanner.nextInt();
            //设置回到上下文中
            String result = "不同意";
            if(a==1){
                result = "同意";
            }
            lrm.setResult("项目经理审核结果:"+result);
            //根据选择的结果和条件来设置下一步
            if(a==1){
                if(lrm.getLeaveDays() > 3){
                    //如果请假天数大于3天,而且项目经理同意了,就提交给部门经理
                    request.setState(new DepManagerState());
                    //继续执行下一步工作
                    request.doWork();
                }else{
                    //3天以内的请假,由项目经理做主,就不用提交给部门经理了,转向审核结束状态
                    request.setState(new  AuditOverState());
                    //继续执行下一步工作
                    request.doWork();
                }               
            }else{
                //项目经理要是不同意的话,也就不用提交给部门经理了,转向审核结束状态
                request.setState(new  AuditOverState());
                //继续执行下一步工作
                request.doWork();
            }           
        }       
    }   
}
处理部门经理的审核,处理后对应审核结束状态
package cn.javass.dp.state.example8;

import java.util.Scanner;

/**
 * 处理部门经理的审核,处理后对应审核结束状态
 */
public class DepManagerState implements LeaveRequestState{
    public void doWork(StateMachine request) {
        //先把业务对象造型回来
        LeaveRequestModel lrm = (LeaveRequestModel)request.getBusinessVO();
        System.out.println("部门经理审核中,请稍候......");

        //模拟用户处理界面,通过控制台来读取数据
        System.out.println(lrm.getUser()+"申请从"+lrm.getBeginDate()+
        "开始请假"+lrm.getLeaveDays()+"天,请部门经理审核(1为同意,2为不同意):");
        //读取从控制台输入的数据
        Scanner scanner = new Scanner(System.in);
        if(scanner.hasNext()){
            int a = scanner.nextInt();
            //设置回到上下文中
            String result = "不同意";
            if(a==1){
                result = "同意";
            }
            lrm.setResult("部门经理审核结果:"+result);
            //部门经理审核过后,直接转向审核结束状态了
            request.setState(new AuditOverState());
            //继续执行下一步工作
            request.doWork();
        }       
    }
}
客户端
package cn.javass.dp.state.example8;

public class Client {
    public static void main(String[] args) {
        //创建业务对象,并设置业务数据
        LeaveRequestModel lrm = new LeaveRequestModel();
        lrm.setUser("小李");
        lrm.setBeginDate("2010-02-08");
        lrm.setLeaveDays(5);

        //创建上下文对象
        LeaveRequestContext request = new LeaveRequestContext();
        //为上下文对象设置业务数据对象
        request.setBusinessVO(lrm);
        //配置上下文,作为开始的状态,以后就不管了
        request.setState(new ProjectManagerState());

        //请求上下文,让上下文开始处理工作
        request.doWork();
    }
}

状态模式的优缺点

  • 1:简化应用逻辑控制
  • 2:更好的分离状态和行为
  • 3:更好的扩展性
  • 4:显式化进行状态转换

- 5:引入太多的状态类

思考状态模式

状态模式的本质

状态模式的本质是:根据状态来分离和选择行为

何时选用状态模式

  • 1:如果一个对象的行为取决于它的状态,而且它必须在运行时刻根据状态来改变它
    的行为。可以使用状态模式,来把状态和行为分离开,虽然分离开了,但状态和
    行为是有对应关系的,可以在运行期间,通过改变状态,就能够调用到该状态对
    应的状态处理对象上去,从而改变对象的行为
  • 2:如果一个操作中含有庞大的多分支语句,而且这些分支依赖于该对象的状态。可
    以使用状态模式,把各个分支的处理分散包装到单独的对象处理类里面,这样,
    这些分支对应的对象就可以不依赖于其它对象而独立变化了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值