设计模式详解——状态模式

云中志

共 3237字,需浏览 7分钟

 ·

2021-10-25 01:14

前言

今天我们来看一个号称策略模式双胞胎的设计模式——状态模式,如它的名字一样,状态模式最核心的设计思路就是将对象的状态抽象出一个接口,然后根据它的不同状态封装其行为,这样就可以实现状态和行为的绑定,最终实现对象和状态的有效解耦。下面我们就来详细看下它的基本原理和实现过程吧。

状态模式

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

要点

  • 状态模式允许一个对象基于内部状态而拥有不同的行为
  • 和程序状态机(PSM)不同,状态模式用类代表状态
  • Context会将行为委托给当前状态对象
  • 通过将每个状态封装进一个类,我们把以后需要做的任何改变局部化了
  • 状态模式和策略模式有相同的类图,但是它们的意图不同
  • 策略模式通常会用行为或算法来配置Context
  • 状态模式允许Context随着状态的改变而改变行为
  • 状态转换可以由State类或Context类控制
  • 使用状态模式通常会导致设计中类的数目大量增加
  • 状态类可以被多个Context示例共享

优缺点

优点
  1. 封装了转换规则。

  2. 枚举可能的状态,在枚举状态之前需要确定状态种类。

  3. 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。

  4. 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块

  5. 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

缺点
  1. 状态模式的使用必然会增加系统类和对象的个数。
  2. 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
  3. 状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。

使用场景

  1. 行为随状态改变而改变的场景。
  2. 条件、分支语句的代替者。

示例

状态接口

首先是状态接口,这个接口是给我们实际的状态对象继承的,这个接口有一个方法doAction,这个方法就是给不同的状态对象实现的,用于处理不同状态下的行为的。

public interface State {
    /**
     * 改变状态的操作
     * @param context
     */

    void doAction(Context context);
}
状态所属者

然后是我们的状态所属者,这个类有一个核心的属性就是我们的State接口。

public class Context {
    private State state;

    public Context(){}

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

    public State getState(){
        return state;
    }
     @Override
    public String toString() {
        return "Context{" +
                "state=" + state +
                '}';
    }
}
状态实现

状态实现者继承了State接口,并实现了doAction方法,在方法内部可以对我们的状态所有者进行对应的操作。

这里是一个启动状态:

public class StopState implements State {

    private String name;

    public StopState() {
        this.name = "stop";
    }

    @Override
    public void doAction(Context context) {
        System.out.println("Context is in stop state");
        context.setState(this);
        System.out.println(context);
    }

    @Override
    public String toString() {
        return "StopState{" +
                "name='" + name + '\'' +
                '}';
    }
}

这里是停止状态

public class StartState implements State{

    private String name;

    public StartState() {
        this.name = "start";
    }

    @Override
    public void doAction(Context context) {
        System.out.println("Context is in start state");
        context.setState(this);
        System.out.println(context);
    }

    @Override
    public String toString() {
        return "StartState{" +
                "name='" + name + '\'' +
                '}';
    }
}
测试代码

这里分别实例化了容器和状态的示例,然后通过示例的doAction方法操作容器

@Test
    public void testState() {
        Context context = new Context();

        StartState startState = new StartState();
        startState.doAction(context);


        StopState stopState = new StopState();
        stopState.doAction(context);

    }
运行结果

可以看到,状态对象的doAction方法执行后,容器对应的状态也发生了改变:

好了,关于状态模式就先说这么多,接下来我们做一个简单的总结。

总结

有用过策略模式或者对策略模式比较熟悉的小伙伴应该发现了:策略模式其实和我们今天分析状态模式特别像,甚至连架构模式都是一样的,所以这里我们有必要说下它们的区别。

首先是策略模式,它其实是将不同的算法封装成不同的策略,然后在具体的策略中实现具体的行为,但是测试本身是被动被选择的,容器选择策略,调用过程发生在容器中,而且策略本身是入参;

而我们今天分析的状态模式,它是将不同状态对应的行为封装,然后由具体的状态操作容器,整个过程更像是状态主动发起的,由状态执行其自己的方法,入参是容器。

这两种设计模式从某种程度上说是可以互相替换的,但是还是要结合具体业务分析的,比如spring boot启动过程中,它用到的就是状态模式,这一点我们在分析spring boot启动过程中也发现了;但如果是涉及到算法层面的内容,比如两个数的加减乘除,显然策略模式才是更好的选择。

总之,学习设计模式除了要了解它的基本原理和应用场景之外,更重要的是,要学会辨识优秀框架中的设计模式(知识,知就是知道,了解,识就是辨识,分析),最终将这些设计模式应用到我们的业务开发之中。

- END -


浏览 30
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报