美文网首页
学好设计模式防被祭天:状态模式

学好设计模式防被祭天:状态模式

作者: 阿菜的博客 | 来源:发表于2017-09-03 11:33 被阅读41次
    状态模式

    为了防止被“杀”了祭天,学点设计模式,并总结下还是有必要的。

    一:理解

    1. 状态模式从另一个角度思考状态转移问题。
    2. 原有逻辑是实体的状态从A变成B。在状态模式中,状态转义过程被抽象成从处于状态A的实体变成处于状态B的实体。
    3. 可以抽象出多个包含实体的状态类。

    二:例子

    你是个富二代。

    你的生活状态很简单,一种是充满能量状态ENERGETIC_STATE,还有一种就是贤者时间状态XIANZHE_STATE。

    当你在贤者时间时,只要休息了,就会变得活力满满。

    当你在充满能量状态时,只要papapa了,就会进入贤者时间。

    当你在贤者时间,就没有兴趣接着再papapa了。

    当你活力满满,就不需要再休息了。

    于是,你叫来程序员小菜帮你抽象一下你的生活状态。

    小菜上来就是一顿敲。

    @Data
    public class FuErDai {
        private static final int XIANZHE_STATE = 0;
        private static final int ENERGETIC_STATE = 1;
    
        private int state;
    
        public FuErDai(int state) {
            this.state = state;
        }
    
        public void rest() {
            if (state == XIANZHE_STATE) {
                System.out.println("当前状态是贤者时间,需要休息!");
                state = ENERGETIC_STATE;
            } else if (state == ENERGETIC_STATE) {
                System.out.println("当前状态是能量满满,不需要休息!");
            }
        }
    
        public void papapa() {
            if (state == XIANZHE_STATE) {
                System.out.println("当前状态是贤者时间,不想papapa!");
            } else if (state == ENERGETIC_STATE) {
                System.out.println("当前状态是能量满满,来一发!");
                state = XIANZHE_STATE;
            }
        }
    }
    

    这是一个很简单的程序,FuErDai类中含有状态属性state,每次想要休息rest或者papapa时,使用if else对当前状态进行判断。

    测试程序:

    public class ClientV1 {
        public static void main(String[] args) {
            FuErDai fuErDai = new FuErDai(0);
            fuErDai.papapa();
            fuErDai.papapa();
            fuErDai.rest();
            fuErDai.rest();
            fuErDai.papapa();
            fuErDai.rest();
        }
    }
    

    输入/输出:

    当前状态是贤者时间,不想papapa!
    当前状态是贤者时间,不想papapa!
    当前状态是贤者时间,需要休息!
    当前状态是能量满满,不需要休息!
    当前状态是能量满满,来一发!
    当前状态是贤者时间,需要休息!

    这段程序简单易懂,你很开心。

    有一天,你突然发现,自己在充满能量和贤者时间以外还有一个状态,就是一半一半状态HALF_STATE。

    在一半一半状态时候:

    1. 你尝试休息,会有百分之五十的概率开始休息,休息完进入活力满满状态。
    2. 你尝试papapa,会有百分五十的概率接受约X,papapa之后进入贤者时间。

    小菜觉得不就是多了一个状态而已,分分钟搞定。

    @Data
    public class FuErDaiV2 {
        private static final int XIANZHE_STATE = 0;
        private static final int ENERGETIC_STATE = 1;
        private static final int HALF_STATE = 2;
    
        private int state;
    
        public FuErDaiV2(int state) {
            this.state = state;
        }
    
        public void rest() {
            if (state == XIANZHE_STATE) {
                System.out.println("当前状态是贤者时间,需要休息!");
                state = ENERGETIC_STATE;
            } else if (state == ENERGETIC_STATE) {
                System.out.println("当前状态是能量满满,不需要休息!");
            } else if (state == HALF_STATE) {
                if (isAccept()) {
                    System.out.println("当前状态是一半一半,可以休息");
                    state = ENERGETIC_STATE;
                } else {
                    System.out.println("当前状态是一半一半,但不休息");
                }
            }
        }
    
        public void papapa() {
            if (state == XIANZHE_STATE) {
                System.out.println("当前状态是贤者时间,不想papapa!");
            } else if (state == ENERGETIC_STATE) {
                System.out.println("当前状态是能量满满,来一发!");
                state = XIANZHE_STATE;
            } else if (state == HALF_STATE) {
                if (isAccept()) {
                    System.out.println("当前状态是一半一半,可以papapa");
                    state = XIANZHE_STATE;
                } else {
                    System.out.println("当前状态是一半一半,但不papapa");
                }
            }
        }
    
        private boolean isAccept() {
            return new Random().nextBoolean();
        }
    }
    

    小菜在rest和papapa方法中增加了对HALF_STATE状态的判断,多了一层if else逻辑。

    你觉得这个程序没问题,就向你的二代朋友们炫耀。

    然而,这些朋友纷纷表示不屑。

    虽然说不出为什么,但总觉得这么多if else不是很优雅。

    炫耀不成反丢脸,你拿起你那把40米的大刀准备杀了这位程序员祭天。

    40米的大刀

    吓得小菜急忙开始对于重构的思考:

    1. 这个程序主要抽象的是富二代状态的转移,可以尝试使用状态模式
    2. 太多的if else的确不利于代码的维护,而且说不准哪天富二代又需要增加状态。

    于是,小菜决定使用状态模式进行尝试。

    他先抽象出一个接口,接口描述的是某状态下的富二代,无论处于什么状态的富二代,都有rest和papapa方法,通过State接口进行约束。

    public interface State {
        void rest();
    
        void papapa();
    }
    

    接着,小菜新建了三个状态类。

    状态类中包含FuErDaiV3属性,因为状态类其实表示的是当前状态下的富二代,rest和papapa方法,还是需要富二代来执行。

    // 贤者时间状态
    public class XianZheState implements State {
        FuErDaiV3 fuErDaiV3;
    
        public XianZheState(FuErDaiV3 fuErDaiV3) {
            this.fuErDaiV3 = fuErDaiV3;
        }
    
        @Override
        public void rest() {
            System.out.println("当前状态是贤者时间,需要休息!");
            fuErDaiV3.setState(fuErDaiV3.getEnergeticState());
        }
    
        @Override
        public void papapa() {
            System.out.println("当前状态是贤者时间,不想papapa!");
        }
    }
    
    // 活力满满状态
    public class EnergeticState implements State {
        private FuErDaiV3 fuErDaiV3;
    
        public EnergeticState(FuErDaiV3 fuErDaiV3) {
            this.fuErDaiV3 = fuErDaiV3;
        }
    
        @Override
        public void rest() {
            System.out.println("当前状态是能量满满,不需要休息!");
        }
    
        @Override
        public void papapa() {
            System.out.println("当前状态是能量满满,来一发!");
            fuErDaiV3.setState(fuErDaiV3.getXianZheSate());
        }
    }
    
    // 一半一半状态
    public class HalfState implements State {
        private FuErDaiV3 fuErDaiV3;
    
        public HalfState(FuErDaiV3 fuErDaiV3) {
            this.fuErDaiV3 = fuErDaiV3;
        }
    
        @Override
        public void rest() {
            if (isAccept()) {
                System.out.println("当前状态是一半一半,可以休息");
                fuErDaiV3.setState(fuErDaiV3.getEnergeticState());
            } else {
                System.out.println("当前状态是一半一半,但不休息");
            }
        }
    
        @Override
        public void papapa() {
            if (isAccept()) {
                System.out.println("当前状态是一半一半,可以papapa");
                fuErDaiV3.setState(fuErDaiV3.getXianZheSate());
            } else {
                System.out.println("当前状态是一半一半,但不papapa");
            }
        }
    
        private boolean isAccept() {
            return new Random().nextBoolean();
        }
    }
    

    因为状态各不同,所以不同状态类对于rest和papapa方法的处理也不一样。

    接下来是重构之后的富二代类FuErDaiV3。

    @Data
    public class FuErDaiV3 {
        private State xianZheSate;
        private State energeticState;
        private State halfState;
        private State state;
    
        public FuErDaiV3() {
            xianZheSate = new XianZheState(this);
            energeticState = new EnergeticState(this);
            halfState = new HalfState(this);
            state = xianZheSate;
        }
    
        public void rest() {
            state.rest();
        }
    
        public void papapa() {
            state.papapa();
        }
    }
    

    富二代类包含三个已知可能会进入的状态,和一个当前状态属性state。

    在构造器新建富二代对象时,需要将本身传入状态类中,分别新建出处于贤者时间的高富帅,处于活力满满状态的高富帅和处于一半一半状态的高富帅。

    并且将当前状态/初始状态设置成处于贤者时间的高富帅。

    当你需要执行rest和papapa方法时,只需直接调用state的对应方法即可。

    在使用了状态模式之后,除了判断百分之五十概率处用了if else,其余的条件判断语句都已被去掉。

    当富二代需要增加状态时候,只需新建状态类,以及略微修改富二代类即可。

    你很满意,于是唱起了hip-hop。

    hip-hop

    三:再理解

    1. 状态模式中,富二代类包含状态属性,状态类中又包含富二代属性。相互引用,不知道该先写哪个类。
    2. 状态模式可以去掉对于当前状态的判断语句,方便日后的维护。
    3. 使得富二代的代码做到最清晰。
    4. 不能做到无修改扩展,当增加新状态时候,需为富二代类增加状态属性,并修改构造器。此外,还需要确认其他状态类在执行方法时,是否会进入新状态。不过总体而言,比使用状态模式之前还是优雅太多了。

    相关文章

      网友评论

          本文标题:学好设计模式防被祭天:状态模式

          本文链接:https://www.haomeiwen.com/subject/pnidjxtx.html