zoukankan      html  css  js  c++  java
  • 「补课」进行时:设计模式(19)——状态模式

    1. 前文汇总

    「补课」进行时:设计模式系列

    2. LOL 中的状态

    感觉我天天在用 LOL 举例子,没办法,都已经 S11 了,而我依然在玩这个游戏。

    LOL 中的英雄有很多状态,有正常状态,有吃了伟哥一样的加速状态,有被对方套了虚弱的虚弱状态,还有被对方控制的眩晕状态。

    下面来看下,在 LOL 中,初始的英雄状态:

    public class Hero {
        //正常状态
        public static final int COMMON = 1;
        //加速状态
        public static final int SPEED_UP = 2;
        //减速状态
        public static final int SPEED_DOWN = 3;
        //眩晕状态
        public static final int SWIM = 4;
        //默认是正常状态
        private int state = COMMON;
        //跑动线程
        private Thread runThread;
        //设置状态
        public void setState(int state) {
            this.state = state;
        }
        //停止跑动
        public void stopRun() {
            if (isRunning()) runThread.interrupt();
            System.out.println("--------------停止跑动---------------");
        }
        //开始跑动
        public void startRun() {
            if (isRunning()) {
                return;
            }
            final Hero hero = this;
            runThread = new Thread(new Runnable() {
                public void run() {
                    while (!runThread.isInterrupted()) {
                        try {
                            hero.run();
                        } catch (InterruptedException e) {
                            break;
                        }
                    }
                }
            });
            System.out.println("--------------开始跑动---------------");
            runThread.start();
        }
        private boolean isRunning(){
            return runThread != null && !runThread.isInterrupted();
        }
        //英雄类开始奔跑
        private void run() throws InterruptedException{
            if (state == SPEED_UP) {
                System.out.println("--------------加速跑动---------------");
                Thread.sleep(2000);//假设加速持续2秒
                state = COMMON;
                System.out.println("------加速状态结束,变为正常状态------");
            }else if (state == SPEED_DOWN) {
                System.out.println("--------------减速跑动---------------");
                Thread.sleep(2000);//假设减速持续2秒
                state = COMMON;
                System.out.println("------减速状态结束,变为正常状态------");
            }else if (state == SWIM) {
                System.out.println("--------------不能跑动---------------");
                Thread.sleep(1000);//假设眩晕持续2秒
                state = COMMON;
                System.out.println("------眩晕状态结束,变为正常状态------");
            }else {
                //正常跑动则不打印内容
            }
        }
    }
    

    场景类:

    public class Client {
        public static void main(String[] args) throws InterruptedException {
            Hero hero = new Hero();
            hero.startRun();
            hero.setState(Hero.SPEED_UP);
            Thread.sleep(2000);
            hero.setState(Hero.SPEED_DOWN);
            Thread.sleep(2000);
            hero.setState(Hero.SWIM);
            Thread.sleep(2000);
            hero.stopRun();
        }
    }
    

    可以看到,我们的英雄在跑动过程中随着状态的改变,我们的英雄会以不同的状态进行跑动。

    但是问题也随之而来,我们的英雄类当中有明显的 if else 结构,这并不是我们希望看到的,接下来,我们看下状态模式。

    3. 状态模式

    3.1 定义

    状态模式的定义如下:

    Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.(当一个对象内在状态改变时允许其改变行为, 这个对象看起来像改变了其类。)

    3.2 通用类图

    • State 抽象状态角色:接口或抽象类, 负责对象状态定义, 并且封装环境角色以实现状态切换。
    • ConcreteState 具体状态角色:每一个具体状态必须完成两个职责: 本状态的行为管理以及趋向状态处理, 通俗地说,就是本状态下要做的事情, 以及本状态如何过渡到其他状态。
    • Context 环境角色:定义客户端需要的接口, 并且负责具体状态的切换。

    状态模式从类图上看比较简单,实际上还是比较复杂的,它提供了一种对物质运动的另一个观察视角, 通过状态变更促使行为的变化。

    类似水的状态变更一样, 一碗水的初始状态是液态, 通过加热转变为、气态, 状态的改变同时也引起体积的扩大, 然后就产生了一个新的行为: 鸣笛或顶起壶盖,瓦特就是这么发明蒸汽机的。

    3.3 通用代码:

    抽象环境角色:

    public abstract class State {
        // 定义一个环境角色,提供子类访问
        protected Context context;
        // 设置环境资源
        public void setContext(Context context) {
            this.context = context;
        }
        // 行为1
        abstract void handle1();
        // 行为2
        abstract void handle2();
    }
    

    具体环境角色:

    public class ConcreteState1 extends State {
        @Override
        void handle1() {
            //本状态下必须处理的逻辑
        }
    
        @Override
        void handle2() {
            //设置当前状态为stat2
            super.context.setCurrentState(Context.STATE2);
            //过渡到state2状态, 由Context实现
            super.context.handle2();
        }
    }
    
    public class ConcreteState2 extends State {
        @Override
        void handle1() {
            //设置当前状态为stat2
            super.context.setCurrentState(Context.STATE1);
            //过渡到state2状态, 由Context实现
            super.context.handle1();
        }
    
        @Override
        void handle2() {
            // 本状态下必须处理的逻辑
        }
    }
    

    具体环境角色:

    public class Context {
        final static State STATE1 = new ConcreteState1();
        final static State STATE2 = new ConcreteState2();
    
        private State concreteState;
    
        public State getCurrentState() {
            return concreteState;
        }
        //设置当前状态
        public void setCurrentState(State currentState) {
            this.concreteState = currentState;
            //切换状态
            this.concreteState.setContext(this);
        }
        public void handle1(){
            this.concreteState.handle1();
        }
        public void handle2(){
            this.concreteState.handle2();
        }
    }
    

    环境角色有两个不成文的约束:

    • 把状态对象声明为静态常量, 有几个状态对象就声明几个静态常量。
    • 环境角色具有状态抽象角色定义的所有行为, 具体执行使用委托方式。
    public class Client {
        public static void main(String[] args) {
            //定义环境角色
            Context context = new Context();
            //初始化状态
            context.setCurrentState(new ConcreteState1());
            //行为执行
            context.handle1();
            context.handle2();
        }
    }
    

    这里我们已经隐藏了状态的变化过程, 它的切换引起了行为的变化。 对外来说, 我们只看到行为的发生改变, 而不用知道是状态变化引起的。

    3.4 优点

    • 避免了过多的 if else 语句的使用,避免了程序的复杂性,提高系统的可维护性。
    • 使用多态代替了条件判断,这样我们代码的扩展性更强,比如要增加一些状态,会非常的容易。
    • 状态是可以被共享的,状态都是由 static final 进行修饰的。

    3.5 缺点

    有优点的同事也会产生缺点,有时候,优点和缺点的产生其实是同一个事实:

    状态模式最主要的一个缺点是:子类会太多,也就是类膨胀。因为一个事物有很多个状态也不稀奇,如果完全使用状态模式就会有太多的子类,不好管理。

    4. 案例完善

    前面那个 LOL 的例子,如果使用状态模式重写一下,会是这样的:

    首先创建一个跑动的接口:

    public interface RunState {
        void run(Hero hero);
    }
    

    接下来是4个实现类,分别实现不同状态的跑动结果:

    public class CommonState implements RunState {
        @Override
        public void run(Hero hero) {
            // 正常跑动则不打印内容,否则会刷屏
        }
    }
    
    public class SpeedUpState implements RunState{
        @Override
        public void run(Hero hero) {
            System.out.println("--------------加速跑动---------------");
            try {
                Thread.sleep(2000);//假设加速持续2秒
            } catch (InterruptedException e) {}
            hero.setState(Hero.COMMON);
            System.out.println("------加速状态结束,变为正常状态------");
        }
    }
    
    public class SpeedDownState implements RunState{
        @Override
        public void run(Hero hero) {
            System.out.println("--------------减速跑动---------------");
            try {
                Thread.sleep(2000);//假设减速持续2秒
            } catch (InterruptedException e) {}
            hero.setState(Hero.COMMON);
            System.out.println("------减速状态结束,变为正常状态------");
        }
    }
    
    public class SwimState implements RunState {
        @Override
        public void run(Hero hero) {
            System.out.println("--------------不能跑动---------------");
            try {
                Thread.sleep(1000);//假设眩晕持续1秒
            } catch (InterruptedException e) {}
            hero.setState(Hero.COMMON);
            System.out.println("------眩晕状态结束,变为正常状态------");
        }
    }
    

    最后是一个 Hero(Context) 类:

    public class Hero {
        public static final RunState COMMON = new CommonState();//正常状态
    
        public static final RunState SPEED_UP = new SpeedUpState();//加速状态
    
        public static final RunState SPEED_DOWN = new SpeedDownState();//减速状态
    
        public static final RunState SWIM = new SwimState();//眩晕状态
    
        private RunState state = COMMON;//默认是正常状态
    
        private Thread runThread;//跑动线程
        //设置状态
        public void setState(RunState state) {
            this.state = state;
        }
        //停止跑动
        public void stopRun() {
            if (isRunning()) runThread.interrupt();
            System.out.println("--------------停止跑动---------------");
        }
        //开始跑动
        public void startRun() {
            if (isRunning()) {
                return;
            }
            final Hero hero = this;
            runThread = new Thread(new Runnable() {
                public void run() {
                    while (!runThread.isInterrupted()) {
                        state.run(hero);
                    }
                }
            });
            System.out.println("--------------开始跑动---------------");
            runThread.start();
        }
    
        private boolean isRunning(){
            return runThread != null && !runThread.isInterrupted();
        }
    }
    

    可以看到,这段代码和开头那段代码虽然完成了一样的功能,但是整个代码的复杂度缺以肉眼可见的级别提高了,一般而言,我们牺牲复杂性去换取的高可维护性和扩展性是相当值得的,除非增加了复杂性以后,对于后者的提升会乎其微。

  • 相关阅读:
    error while loading shared libraries: libhwloc.so.5: cannot open shared object file: No such file or directory
    Mac刷新DNS,修改/etc/hosts立即生效
    zxing生成识别二维码
    电脑M2接口不是磁盘0问题
    WSL
    Ubuntu安装Docker
    Win10关闭自动更新和防护
    Mysql的5种日志
    开发常用工具Windows
    log4j2高危漏洞
  • 原文地址:https://www.cnblogs.com/babycomeon/p/14136877.html
Copyright © 2011-2022 走看看