设计模式-状态模式

2,569 阅读5分钟

# 七大软件设计原则
# 设计模式-工厂模式
# 设计模式-单例模式
# 设计模式-原型模式
# 设计模式-策略模式
# 设计模式-责任链模式
# 设计模式-建造者模式
# 设计模式-深度分析代理模式
# 设计模式-门面模式
# 设计模式-装饰器模式
# 设计模式-享元模式
# 设计模式-组合模式
# 设计模式-适配器模式
# 设计模式-桥接模式
# 设计模式-委派模式
# 设计模式-模板方法模式
# 设计模式-迭代器模式
# 设计模式-命令模式 # 设计模式-备忘录模式

状态模式(State Pattern )也称为状态机模式(State Machine Pattern),是允许对象在内 部状态发生改变时改变它的行为,对象看起来好像修改了它的类,属于行为型模式。

状态模式中类的行为是由状态决定的,不同的状态下有不同的行为。其意图是让一个对象在 其内部改变的时候,其行为也随之改变。状态模式核心是状态与行为绑定,不同的状态对应不同的行为。

适用场景

  1. 行为随状态改变而改变的场景
  2. 一个操作中含有庞大的多分支结构,并且这些分支取决于对象的状态

简单来说状态模式主要解决的就是当控制一个对象状态的条件表达式过于复杂时的情况。通过把状态 的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。对象的行为依赖 于它的状态(属性),并且会根据它的状态改变而改变它的相关行为。

好处是客户端只需要调用自己需要的操作即可。

UML类图

image.png

具体代码如下:

public interface IState {
    void handle();
}
public class ConcreteStateA implements IState{
    @Override
    public void handle() {
        System.out.println("状态A下的操作");
    }
}
public class ConcreteStateB implements IState{
    @Override
    public void handle() {
        System.out.println("状态B下的操作");
    }
}
public class Context {
    private IState state;

    private final IState STATE_A = new ConcreteStateA();
    private final IState STATE_B = new ConcreteStateB();

    {
        state = STATE_A;
    }

    public void setState(IState state) {
        this.state = state;
    }

    public void handle(){
        state.handle();
        //下一次调用变成状态B调用
        setState(STATE_B);
    }

}
public class Test {
    public static void main(String[] args) {
        Context context = new Context();
        context.handle();
    }
}

代码示例

举个验证码登录的例子,假设我们在设计我们的系统登录功能,首先肯定不能让用户一直不停的获取短信然后登录吧,如果是这样那么光这个短信费就能把公司搞死了,所以一般是这样设计的,一天只能有几次获取登录短信的机会用完了只能等到明天在获取(当然更精细的需求可能是短时间内有几次机会用完过两三个小时在给几次机会在用完就等到第二天,这里我们仅仅做一个demo没必要搞那么麻烦),这个时候我们就可以提取出来两个状态,一个是正常获取短信验证码的状态、第二个就是获取短信验证码次数达到上线这两种状态。

首先先把状态的抽象类提取出来如下:

public interface ISmsState {
    Integer getSmsCode();
}

然后分别实现两种状态:

public class NormalSmsState implements ISmsState {
    @Override
    public Integer getSmsCode() {
        return (int) (Math.random()*9000+1000);
    }
}
public class AbnormalSmsState implements ISmsState{
    @Override
    public Integer getSmsCode() {
        System.out.println("获取验证码的次数已达到上线");
        return null;
    }
}

创建环境类角色

public class SmsManager {
    private ISmsState state;
    private final HashMap<Integer,Integer> userCount = new HashMap<>();

    private final ISmsState NORMAL = new NormalSmsState();
    private final ISmsState ABNORMAL = new AbnormalSmsState();

    public Integer getSmsCode(Integer userId) {
        if(userCount.containsKey(userId)){
            if(userCount.get(userId) >= 5){
                state = ABNORMAL;
            }else{
                state = NORMAL;
            }
            userCount.put(userId,userCount.get(userId) + 1);
        }else{
            state = NORMAL;
            userCount.put(userId,1);
        }
        return state.getSmsCode();
    }
}

客户端调用

public class Test {
    public static void main(String[] args) {
        SmsManager sms = new SmsManager();
        for (int i = 0; i < 7; i++) {
            System.out.println(sms.getSmsCode(1111));
        }
    }
}

image.png

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

状态模式和策略模式以及责任链模式的区别

首先是状态模式和责任链模式,责任链是不知道自己下一个处理对象是谁的,责任链的联调是客户端去链接的,而状态模式,自己是知道下一个状态是什么的。

状态模式和策略模式的区别,其实上文中的例子可能大家看到都会觉得这不就是策略模式吗,只能说可以使用策略模式实现,他们有一个最大的区别是策略模式只能挑一种算法去实现(用户去选择其中一种)并且每个算法之间是没有相互关系的,而状态模式各个状态之间是有某种联系的并且用户是不能去指定状态的,只能是初始化状态

状态模式的优缺点

优点:

  1. 结构清晰:将状态独立为类,消除了客户端冗余的if…else 或 switch..case 语句,使代码更加简洁, 提高系统可维护性;
  2. 将状态转换显示化:通常的对象内部都是使用数值类型来定义状态,状态的切换是通过赋值 进行表现,不够直观;而使用状态类,在切换状态时,是以不同的类进行表示,转换目的更加 明确;
  3. 状态类职责明确且具备扩展性。

缺点:

  1. 类数量增加结构变得更复杂
  2. 开闭原则的支持不好如果需要增加一个状态或者减少一个状态肯定是要改对应的方法的