前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计之禅——状态模式

设计之禅——状态模式

作者头像
夜勿语
发布2020-09-07 10:45:38
2870
发布2020-09-07 10:45:38
举报
文章被收录于专栏:Java升级之路Java升级之路

前言

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

详解

普通实现

首先我们来分析一个实例:现在的游戏基本都有自动打怪做任务的功能,如果让你实现这个功能你会怎么做呢?

本篇讲解的是状态模式,当然首先应该分析其应有状态和行为,下面是我画的一个简单的状态图:

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

代码语言:javascript
复制
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() {
        
    }

}

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

代码语言:javascript
复制
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原则的,我们没有封装变化的那部分。那应该如何做呢?这就需要我们的状态模式了。

使用状态模式重构代码

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

代码语言:javascript
复制
public interface State {

    void accept(int count);

    void move();

    void attack();

    void submit();

}

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

代码语言:javascript
复制
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方法,而角色的行为则全都委托给了具体的状态类来实现,那具体的状态类应该如何实现呢?

代码语言:javascript
复制
// 初始状态
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接口就行了,看起来相当完美。但是,没有什么模式是完美的,使用状态模式的缺点我们很容易发现,原来一个类就能解决的,现在裂变为了四个类,系统结构复杂了很多,但这样的牺牲是非常有必要和值得的。

思考

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

代码语言:javascript
复制
    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类还是状态类对修改封闭:

代码语言:javascript
复制
c.setCurrent(c.getHasMonsterState());

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

总结

状态模式允许对象在内部状态改变时改变它的行为,如果需要在多个对象间共享状态,那么只需要定义静态域即可。

状态模式与策略模式具有相同的类图,但它们本质的意图是不同的。前者是封装基于状态的行为,并将行为委托到当前的状态,用户不需要知道有哪些状态;而后者是将可以互换的行为封装起来,然后使用委托,由客户决定需要使用哪种行为,客户需要知道所有的行为类。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-02-06 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 详解
    • 普通实现
      • 使用状态模式重构代码
        • 思考
        • 总结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档