zoukankan      html  css  js  c++  java
  • 设计之禅——状态模式

    前言

    之前我写过一篇策略模式的文章,讲的是如何灵活地改变对象的行为,今天要讲的模式和策略模式非常像,它也是让你设计出如何灵活改变对象行为的一个模式,与策略模式不同的是它是根据自身状态而自行地改变行为,它就是状态模式。

    详解

    普通实现

    首先我们来分析一个实例:现在的游戏基本都有自动打怪做任务的功能,如果让你实现这个功能你会怎么做呢?
    本篇讲解的是状态模式,当然首先应该分析其应有状态和行为,下面是我画的一个简单的状态图:
    state
    椭圆代表的是所处状态,指引线代表执行的行为。一开始角色处于初始状态,什么也不做,当玩家开启自动任务功能时,角色就自动的接受任务,当接到杀怪的任务后,发现周围没有怪,就把“初始状态”改为“未发现怪物”状态并开始四处游走寻找怪物,走啊走,走啊走,发现了目标怪物就将状态修改为“发现怪物”,然后开始攻击打怪,直到杀怪数量达到任务指定数量后,就停止打怪并将状态修改为“任务达成”状态,最后回到接任务那里提交任务,角色状态又重置为初始状态(这里只是为了方便理解该模式,不要太纠结功能细节)。不难发现,在该实例中,我们包含了四个状态和四个行为,任何一个行为是随时都有可能进行的,但是其表现结果却会因为状态的不同而有不一样的结果,按照我们面向过程的编程方式也是非常容易实现的:

    public class Character {
    
        // 停止
        private final static int STOP = 0;
        // 附近有怪
        private final static int HASMONSTER = 1;
        // 附近没有怪
        private final static int NOMONSTER = 2;
        // 任务条件达成
        private final static int MISSIONCLEAR = 4;
    
        // 当前状态
        private int state = STOP;
        // 还需杀怪数量
        private int count = 0;
    
        public void accept(int count) {
            if (state == STOP) {
                this.count = count;
                state = NOMONSTER;
                // move to find the monster
                move();
            } else if (state == HASMONSTER) {
                System.out.println("Sorry!You are doing the task,so you can't accept the new task!!");
            } else if (state == NOMONSTER) {
                System.out.println("Sorry!You are doing the task,so you can't accept the new task!!");
            } else if (state == MISSIONCLEAR) {
                System.out.println("Sorry!You must submit the current task!");
            }
        }
    
        private void move() {
            if (state == STOP) {
                System.out.println("Moving....");
                state = HASMONSTER;
                attack();
            } else if (state == HASMONSTER) {
                System.out.println("Moving to find new monster");
                attack();
            } else if (state == NOMONSTER) {
                System.out.println("Moving to find monster");
                state = HASMONSTER;
                attack();
            } else if (state == MISSIONCLEAR) {
                System.out.println("Moving to submit");
                submit();
            }
        }
    
        private void attack() {
            
        }
    
        private void submit() {
            
        }
    
    }
    
    

    最后两个方法我没有给出具体实现,相信难不倒你,当全部实现后角色就能自动接任务打怪了:

    Accept the task.Need to kill monster:10
    Moving to find monster
    need to kill:9
    need to kill:8
    need to kill:7
    need to kill:6
    need to kill:5
    need to kill:4
    need to kill:3
    need to kill:2
    need to kill:1
    need to kill:0
    Moving to submit
    Congratulations on completing the task!
    

    不过,功能虽然实现了,但是这样写代码冗长不说,还非常难于理解维护,想象一下这里只假设了4种状态,当如果有非常多的状态,那就是满篇的if else了,而且如果未来需要增加新的状态,那么当前的实现无疑是违反了open-close原则的,我们没有封装变化的那部分。那应该如何做呢?这就需要我们的状态模式了。

    使用状态模式重构代码

    往下看之前,不妨先仔细思考一下,既然该功能中状态是会随时改变的,而行为又会受到状态的影响,那何不将状态抽离出来成为一个体系呢?比如定义一个状态接口(为什么这里需要定义所有的行为方法呢?):

    public interface State {
    
        void accept(int count);
    
        void move();
    
        void attack();
    
        void submit();
    
    }
    

    那么角色类中就可以如下定义了:

    public class Character {
    
        // 当前状态
        private State current = new StopState(this);
        // 所需杀怪数量
        private int count = 0;
    
        public void accept(int count) {
            // 注意这里不能直接将值赋给成员变量
            current.accept(count);
        }
    
        public void move() {
            current.move();
        }
    
        public void attack() {
            current.attack();
        }
    
        public void submit() {
            current.submit();
        }
    
        public void killOne() {
            this.count--;
        }
    
        public void setCurrent(State current) {
            this.current = current;
        }
    
        public void setCount(int count) {
            this.count = count;
        }
    
        public State getCurrent() {
            return current;
        }
    
        public int getCount() {
            return count;
        }
    }
    

    相比较之前,新的类只保留了当前状态,并增加了getter和setter方法,而角色的行为则全都委托给了具体的状态类来实现,那具体的状态类应该如何实现呢?

    // 初始状态
    public class StopState implements State {
        private Character c;
    
        public StopState(Character c) {
            this.c = c;
        }
    
        @Override
        public void accept(int count) {
            c.setCount(count);
            c.setCurrent(new NoMonsterState(c));
            c.move();
        }
    
        @Override
        public void move() {
            System.out.println("Moving....");
            c.setCurrent(new HasMonsterState(c));
            c.attack();
        }
    
        @Override
        public void attack() {
            System.out.println("Sorry!You must accept the task!");
        }
    
        @Override
        public void submit() {
            System.out.println("You don't have task to submit!");
        }
    }
    
    // 附近没有怪物
    public class NoMonsterState implements State {
        private Character c;
    
        public NoMonsterState(Character c) {
            this.c = c;
        }
    
        @Override
        public void accept(int count) {
            System.out.println("Sorry!You are doing the task,so you can't accept the new task!!");
        }
    
        @Override
        public void move() {
            System.out.println("Moving to find monster!");
            c.setCurrent(new HasMonsterState(c));
            c.attack();
        }
    
        @Override
        public void attack() {
            c.move();
        }
    
        @Override
        public void submit() {
            System.out.println("Please complete the task!");
        }
    }
    

    这里我也只给出了两个实现类,其它的相信你能很容实现它们。通过状态模式重构后,代码清晰了很多,没有满屏的if else,角色也能够根据当前所处的状态表现出相应的行为,同时如果需要增加新的状态时,只需要实现State接口就行了,看起来相当完美。但是,没有什么模式是完美的,使用状态模式的缺点我们很容易发现,原来一个类就能解决的,现在裂变为了四个类,系统结构复杂了很多,但这样的牺牲是非常有必要和值得的。

    思考

    刚刚我们已经实现了状态模式,但是还有个细节问题不知你注意到了没有?比如:

        public void move() {
            System.out.println("Moving....");
            c.setCurrent(new HasMonsterState(c));
            c.attack();
        }
    

    在我的实现中,都是由状态来控制下一个状态是什么,这样状态之间就形成了强依赖,当然你可以将状态转换放到context(Character)类中,不过这种更适合状态转换是固定的,而在我们这个例子中,状态的变更是动态的。还需要注意的是我这里调用 c.setCurrent(new HasMonsterState©)时,状态是硬编码传入的,这样当系统进化时可能就需要更改此处的代码,如何解决这种情况呢?在《Head First设计模式》书中有提到,在Context类中定义所有的状态并提供getter方法,这里则调用getter获取后再传入,但区别只在于是context类还是状态类对修改封闭:

    c.setCurrent(c.getHasMonsterState());
    

    对此我有点疑问,即使使用getter获取,那未来系统进化导致状态的改变后难道不需要修改getter方法名么?

    总结

    状态模式允许对象在内部状态改变时改变它的行为,如果需要在多个对象间共享状态,那么只需要定义静态域即可。
    状态模式与策略模式具有相同的类图,但它们本质的意图是不同的。前者是封装基于状态的行为,并将行为委托到当前的状态,用户不需要知道有哪些状态;而后者是将可以互换的行为封装起来,然后使用委托,由客户决定需要使用哪种行为,客户需要知道所有的行为类。

  • 相关阅读:
    Oracle数据表解锁
    VS2008 新建网站时没有模板解决办法
    64位WIN2008中组件服务中的DCOM配置找不到Microsoft Excel应用程序的解决办法
    Log4Net Appender配置
    使用过滤器过滤asp.net mvc输出内容
    Html.DropDownList也能绑定数据
    iirf有时有效,有时返回404错误的解决方法
    Oracle优化AutoTrace输出内容的含义
    Oracle使用Sql把XML解析成表(Table)的方法
    文件跨系统ftp传输后无法还原了,哎
  • 原文地址:https://www.cnblogs.com/yewy/p/13111843.html
Copyright © 2011-2022 走看看