【Design Pattern】Behavioural - State

Posted by 西维蜀黍 on 2018-11-11, Last Modified on 2022-12-10

动机

  • 一个对象可能会拥有不同的状态,且可在运行时(runtime)在这几个状态之间切换;
  • 当这个对象与外界交互时,他的状态就可能发生变化,并在不同状态之间切换;
  • 当在状态变化时,对象可表现出不同的行为(以体现状态变化后的影响)。

比如,在一个游戏中,一个游戏角色可处于不同的状态:健康、受伤、死亡:

  • 当处于健康状态时,允许其他角色使用武器对其进行射击;
  • 当被其他角色射击命中时(生命值下降),处于受伤状态;
  • 当生命值下降至0时,处于死亡状态。

同时,当状态变化时,触发相应的行为或逻辑,比如:

  • 当处于受伤状态后,允许通过吃食物来增加生命值(设置一个Boolean值表示是否允许吃食物,并置为True);
  • 当处于死亡状态后,则不会被攻击(或者说被攻击后,生命值不再下降)。

定义

用一句话来表述,状态模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。

特点

  • 一个对象可拥有不同的状态
  • 可在不同状态之间不断的切换
  • 某一瞬间,只能处于一个特定的唯一状态(而不能同时处于多种状态)

构成

  • 一个状态接口(State Interface) ;
  • 一个或多个具体的状态角色(Concrete State),且实现了状态接口;
  • 状态上下文角色,以允许调用者指定特定的状态;

UML图

实现

环境角色类

public class Context {
    //持有一个State类型的对象实例
    private State state;

    public void setState(State state) {
        this.state = state;
    }
    /**
     * 用户感兴趣的接口方法
     */
    public void request(String sampleParameter) {
        //转调state来处理
        state.handle(sampleParameter);
    }
}

抽象状态类

public interface State {
    /**
     * 状态对应的处理
     */
    public void handle(String sampleParameter);
}

具体状态类

public class ConcreteStateA implements State {
    @Override
    public void handle(String sampleParameter) {
        System.out.println("ConcreteStateA handle :" + sampleParameter);
    }
}
public class ConcreteStateB implements State {
    @Override
    public void handle(String sampleParameter) {
        System.out.println("ConcreteStateB handle :" + sampleParameter);
    }
}

客户端类

public class Client {
    public static void main(String[] args){
        //创建状态
        State state = new ConcreteStateB();
        //创建环境
        Context context = new Context();
        //将状态设置到环境中
        context.setState(state);
        //请求
        context.request("test");
    }
}

从上面可以看出,环境类Context的行为request()是委派给某一个具体状态类的。通过使用多态性原则,可以动态改变环境类Context的属性State的内容,使其从指向一个具体状态类变换到指向另一个具体状态类,从而使环境类的行为request()由不同的具体状态类来执行。

示例

实现思路

  • 定义Player类表示游戏角色
  • 定义一个PlayerState接口
  • 定义不同的状态类(HealthyState, SurvivalState, DeadState),且它们均实现了PlayerState接口
  • 一个上下文类(GameContext),并包含一个setState()方法:

实现

Player.java

public class Player {

    public void attack() {
        System.out.println("Attack");
    }

    public void survive() {
        System.out.println("Surviving!");
    }

    public void dead() {
        System.out.println("Dead! Game Over");
    }

}

PlayerState.java

public interface PlayerState {    
    void action(Player p);
}

各种状态

public class HealthyState implements PlayerState {

    @Override
    public void action(Player p) {
        p.attack();
        p.fireBumb();
        p.fireGunblade();
        p.fireLaserPistol();
    }
}

public class SurvivalState implements PlayerState {

    @Override
    public void action(Player p) {
        p.survive();
        p.firePistol();
    }
}

public class DeadState implements PlayerState {

    @Override
    public void action(Player p) {
        p.dead();
    }
}

GameContext.Java

一个上下文类(GameContext),并包含一个setState()方法:

public class GameContext {
    
    private PlayerState state = null;
    private Player player = new Player();

    public void setState(PlayerState state) {
        this.state = state;
    }

    public void gameAction() {
        state.action(player);
    }
}

GameTest

public class GameTest {

    public static void main(String[] args) {

        GameContext context = new GameContext();

        context.setState(new HealthyState());
        context.gameAction();
        System.out.println("*****");

        context.setState(new SurvivalState());
        context.gameAction();
        System.out.println("*****");

        context.setState(new DeadState());
        context.gameAction();
        System.out.println("*****");
    }
}

输出

Attack
Fire Bomb
Fire Gunblade
Laser Pistols
*****
Surviving!
Fire Pistol
*****
Dead! Game Over
*****

UML图

待补充

讨论

我们是否可以移除PlayerState接口和其对应的子类,而简化成只通过if-else if来实现:

public class GameContext {

    private Player player = new Player();

    public void gameAction(String state) {
        if (state == "healthy") {
            player.attack();
            player.fireBumb();
            player.fireGunblade();
            player.fireLaserPistol();
        } else if (state == "survival") {
            player.survive();
            player.firePistol();
        } else if (state == "dead") {
            player.dead();
        }
    }
}

从功能实现的角度来说,是没有问题的。

然而,这样的实现违反了开闭原则(Open-Closed Principle),且具有相对较差的可维护性(Maintainability)。

优缺点

Reference