zoukankan      html  css  js  c++  java
  • 设计模式详解——状态模式

    前言

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

    状态模式

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

    要点

    • 状态模式允许一个对象基于内部状态而拥有不同的行为
    • 和程序状态机(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启动过程中也发现了;但如果是涉及到算法层面的内容,比如两个数的加减乘除,显然策略模式才是更好的选择。

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

  • 相关阅读:
    谷歌浏览器调试
    建立标准编码规则(五)-工程结构
    Ruby页面,循环赋值方法(类似java EL表达式赋值)
    ruby--Hash方法汇总
    ant design环境搭建过程中遇到的问题--Windows-dva-cli
    Linux下Nodejs安装(完整详细)
    linux chmod命令使用
    EL表达式获取日期时间类型后格式化的问题
    百度网盘视频资源
    Eclipse插件:mybatis generator的使用步骤
  • 原文地址:https://www.cnblogs.com/caoleiCoding/p/15449802.html
Copyright © 2011-2022 走看看