策略模式的双胞胎:状态模式

Posted hongdouni

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了策略模式的双胞胎:状态模式相关的知识,希望对你有一定的参考价值。

Simple Demo

假设我有一部iPhoneX,又非常喜欢玩游戏,那么我这部破手机主要存在两种状态:待机和游戏中。

此时手机的状态图非常简单:

 

  技术分享图片

 

将这个状态图转换为代码:

每一个状态用不同的整数代表,将每一个动作整合成方法,每一个动作都可能造成状态的转换。

public class MyiPhoneX {
    public final static int    STANDBY    = 0;        // 待机状态
    public final static int    PLAYING    = 1;        // 游戏进行中状态
    private int                state    = STANDBY;    // 持有当前状态的实例变量

    public MyiPhoneX() {
    }

    public void startGame() { // 打开游戏
        if (state == STANDBY) {
            System.out.println("Game is loading...");
            state = PLAYING;
        } else if (state == PLAYING) {
            System.out.println("Game is already in progress!");// 已存在该游戏进程
        }
    }

    public void exitGame() { // 结束游戏
        if (state == STANDBY) {
            System.out.println("There is no game process!");// 不存在游戏进程,这是此状态的一个不恰当动作
        } else if (state == PLAYING) {
            System.out.println("Game is exiting...");
            state = STANDBY;
        }
    }
}

 测试代码:

public class MyiPhoneXTest {
    public static void main(String[] args) {
        MyiPhoneX iPhoneX = new MyiPhoneX();
        iPhoneX.startGame();
        iPhoneX.exitGame();
        iPhoneX.exitGame();
        iPhoneX.startGame();
        iPhoneX.exitGame();
    }
}
Game is loading...
Game is exiting...
There is no game process!
Game is loading...
Game is exiting...

 

更改需求

但存在一种特殊情况:遇上了某款“渣渣游戏”,其在响应网络方面的功能没有很完善,我的iPhoneX在加载这款游戏的过程中某个瞬间网络状况不太好,进入了“游戏无限加载”状态,最后只能选择退出游戏后再重新尝试打开游戏。

下面是加入“无限加载状态”的手机状态图:

 

  技术分享图片

 

如果在第原版本的代码进行维护, 需要作如下修改:

 

  技术分享图片

 

可这样的代码不易于扩展,我的iPhoneX每新增一个状态,需要MyiPhoneX类做出的改变地方可能会更多,这样的设计不符合“对修改关闭,对扩展开放”的原则、也不够面向对象。

而且这还只是一个只有两三个状态的简单例子,如果是状态及操作方法较多的程序,按照以上的设计,代码臃肿混乱,每新增一个状态,需要在每个操作方法里都新增一个 else if 判断,所有的代码维护都堆积在一个类中。

 

新的设计

为了更容易的维护和修改程序,我们需要对原代码重新设计:

我们将每个状态的行为都放在各自的类中(将会改变的那部分封装起来),那么每个状态类只要实现它自己的动作就可以了,对iPhoneX执行的动作只需要委托当前状态状态对象具体执行就好了。我们可以这么做:

①   先定义一个State接口,iPhoneX可以执行的每一个动作都有对应的方法在这个接口内。(面向接口编程

②   iPhoneX的每个状态都有一个对应的实现了State接口的具体状态类

③   对iPhoneX执行动作委托给具体状态类来执行。

状态接口和其具体实现类的类图如下:

 

  技术分享图片

 

实际代码:

public interface State {
    void startGame();

    void exitGame();
}

 

具体状态实现类主要做的是实现适合所在的这个状态的具体动作。

 

public class Standby implements State {  //“待机”状态
    private MyiPhoneX    iPhoneX;

    public Standby(MyiPhoneX iPhoneX) {
        this.iPhoneX = iPhoneX;
    }

    @Override
    public void startGame() {
        System.out.println("Game is loading..."); // 游戏正在加载
        iPhoneX.setState(iPhoneX.getPlaying()); // iPhone修改为“游戏中状态”
    }

    @Override
    public void exitGame() {
        System.out.println("There is no game process!");// 不存在该游戏进程,这是此状态的一个不恰当动作
    }
}

 

 

public class Playing implements State {  //“游戏中”状态
    private MyiPhoneX iPhoneX;

    public Playing(MyiPhoneX iPhoneX) {
        this.iPhoneX = iPhoneX;
    }

    @Override
    public void startGame() {
        System.out.println("Game is already in progress!");// 已存在该游戏进程
    }

    @Override
    public void exitGame() {
        System.out.println("Game is exiting..."); // 退出游戏中
        iPhoneX.setState(iPhoneX.getStandby()); // iPhone修改为“待机状态”
    }
}

 

 

我的iPhoneX实体类:

public class MyiPhoneX {
    private State    standby;
    private State    playing;
    private State    state    ;    // 持有当前状态的实例变量

    public MyiPhoneX() {
        standby = new Standby(this);
        playing = new Playing(this);
        state = standby;
    }

    public void startGame() { // 打开游戏
        state.startGame();
    }

    public void exitGame() { // 结束游戏
        state.exitGame();
    }

    public void setState(State state) { // 用于修改手机当前状态
        this.state = state;
    }

    public State getStandby() {
        return standby;
    }

    public State getPlaying() {
        return playing;
    }
}

 

实现需求

将新的“游戏无限加载”状态加上,代码修改如下:

增加遇到渣渣游戏的“游戏无限加载”状态类:

public class RubbishGame implements State { //“游戏无限加载”状态
    private MyiPhoneX iPhoneX;

    public RubbishGame(MyiPhoneX iPhoneX) {
        this.iPhoneX = iPhoneX;
    }

    @Override
    public void startGame() {
        System.out.println("Game is already in progress!");// 已存在该游戏进程
    }

    @Override
    public void exitGame() {
        System.out.println("Game is exiting..."); // 退出游戏中
        iPhoneX.setState(iPhoneX.getStandby()); // iPhone修改为“待机状态”
    }
}

 

在MyiPhoneX类中添加引用“游戏无限加载”状态的实例变量:

public class MyiPhoneX {
    private State    standby;
    private State    playing;
    private State    rubbishGame; //添加遇到渣渣游戏的“游戏无限加载”状态
    private State    state;        // 持有当前状态的实例变量

    public MyiPhoneX() {
        standby = new Standby(this);
        playing = new Playing(this);
        rubbishGame = new RubbishGame(this); //初始化“游戏无限加载”状态
        state = standby;
    }

    public void startGame() { // 打开游戏
        state.startGame();
    }

    public void exitGame() { // 结束游戏
        state.exitGame();
    }

    public void setState(State state) { // 用于修改手机当前状态
        this.state = state;
    }

    public State getStandby() {
        return standby;
    }

    public State getPlaying() {
        return playing;
    }

    public State getRubbishGame() { //提供获取“游戏无限加载”状态的方法
        return rubbishGame;
    }
}

 

修改“待机”状态类,我们用随机数代表遇到“渣渣游戏”的机会,增加从“待机”到“游戏无限加载”状态的转换的代码:

public class Standby implements State {
    private MyiPhoneX    iPhoneX;
    private Random        randomRubbish    = new Random(); //用于产生随机数

    public Standby(MyiPhoneX iPhoneX) {
        this.iPhoneX = iPhoneX;
    }

    @Override
    public void startGame() {
        int rubbish = randomRubbish.nextInt(10);
        if (rubbish <= 1) {
            System.out.println("Rubbish Game!"); // 游戏正在加载
            iPhoneX.setState(iPhoneX.getRubbishGame()); // iPhone修改为“游戏中状态”
        } else {
            System.out.println("Game is loading..."); // 游戏正在加载
            iPhoneX.setState(iPhoneX.getPlaying()); // iPhone修改为“游戏中状态”
        }

    }

    @Override
    public void exitGame() {
        System.out.println("There is no game process!");// 不存在该游戏进程,这是此状态的一个不恰当动作
    }

}

测试代码:

 

public class MyiPhoneXTest {
    public static void main(String[] args) {
        MyiPhoneX iPhoneX = new MyiPhoneX();
        iPhoneX.startGame();
        iPhoneX.exitGame();
        iPhoneX.startGame();
        iPhoneX.exitGame();
        iPhoneX.startGame();
        iPhoneX.exitGame();
        iPhoneX.startGame();
        iPhoneX.exitGame();
    }
}
Rubbish Game!
Game is exiting...
Game is loading...
Game is exiting...
Rubbish Game!
Game is exiting...
Game is loading...
Game is exiting...

 

这样就为我的iPhoneX增加了一个新的状态,并实现了这个新的状态。要做的只是新增这个状态类及增加能够转换到这个状态的代码。

相对最早的代码版本,删除了容易产生问题的if语句,日后更好维护,让大多数状态类“对修改关闭”,让MyiPhoneX“对扩展开放”,而且这样的代码结构,对应起相关的状态图也更容易阅读和理解。

在这个比较简单的例子好处看起来可能不是很明显;假如是在多个状态的程序,我们新增一个状态,只需新增这个状态的实体类、在主体类中加入这个新状态类的实例引用、在能够转换到这个状态的其他状态类加入转换的逻辑代码

 

状态模式:

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

这个模式将状态封装成独立的类,并将动作委托到代表当前状态的对象,我的iPhoneX在“待机”和“游戏中”两种不同状态时,如果执行“开始游戏”的动作,就会得到不同的具体行为(提示已开启游戏和启动游戏)。

我们使用组合通过简单引用不同的状态对象来造成类改变的假象(执行一个动作时,当前的状态对象的具体动作里可能会转换内部的状态,而客户的视角是主体对象自己发生了改变)。

状态模式的类图:

 

  技术分享图片

 

我们可以发现,它和策略模式的类图是一样的。

两个模式的差别在于它们的“意图”不同。

①对状态模式而言

将具体行为封装在状态类中,Context的行为委托给当前具体状态对象来执行,执行具体行为后可能会改变当前状态,这样Context执行的具体行为也在改变(每个状态类对每种行为都有各自的具体实现)。而使用Context的客户对于状态对象却是陌生的、甚至是未知的。这个方案可以去掉原本存在Context内的许多的条件判断,通过Context对象内简单的改变状态对象来改变行为。

②对策略模式而言

客户对于策略对象是很熟悉的,通常由客户主动选择Context需要组合的策略对象,而且虽然策略模式可以在运行时改变策略,但对于某个Context对象来说,通常只有一个最适当的策略对象。一般用来作为除了继承之外的一种弹性替代方案,通过组合不同对象来改变行为。

 

参考书籍:《Head First 设计模式》


以上是关于策略模式的双胞胎:状态模式的主要内容,如果未能解决你的问题,请参考以下文章

工厂模式 VS 策略模式

尝试使用片段保存夜间模式状态

设计模式—策略模式 状态模式

Java中,状态模式和策略模式的区别

孪生兄弟状态模式与策略模式有什么区别,究竟该如何选择

Java重构-策略模式状态模式卫语句