设计模式——状态模式

概述

状态模式:(State Pattern)允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类

状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。

何时使用:

  • 状态模式一般用来实现状态机,而状态机常用在游戏、工作流引擎等系统开发中。

  • 代码中包含大量与对象状态有关的条件语句。

什么是状态机?

  • 状态机有 3 个组成部分:状态(State)、事件(Event)、动作(Action)。其中,事件也称为转移条件(Transition Condition)。事件触发状态的转移及动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何动作。

UML 类图:

image.png

角色组成:

  1. 上下文环境(Context):它定义了客户程序需要的接口并维护一个具体状态角色的实例,将与状态相关的操作委托给当前的Concrete State对象来处理。

  2. 抽象状态(State):定义一个接口以封装使用上下文环境的的一个特定状态相关的行为。

  3. 具体状态(Concrete State):实现抽象状态定义的接口。

通用代码

Context.java

public class Context {
    private State state;

    public Context() {
        this.state = new ConcreteStateA();  // 设置初始状态
    }

    public State getState() {
        return state;
    }

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

    public void request() {
        this.state.handle(this);
    }

}

State.java

public abstract class State {

    public abstract void handle(Context context);

}

ConcreteStateA.java、ConcreteStateB.java

public class ConcreteStateA extends State {

    @Override
    public void handle(Context context) {
        System.out.println("ConcreteStateA 状态的 handle 方法");
        context.setState(this);
    }
}


public class ConcreteStateB extends State {

    @Override
    public void handle(Context context) {
        System.out.println("ConcreteStateB 状态的 handle 方法");
        context.setState(this);
    }
}

测试:

public class Test {
    public static void main(String[] args) {

        Context context = new Context();
        context.request();
        System.out.println("当前状态为:" + context.getState());

        context.setState(new ConcreteStateB());
        context.request();
        System.out.println("当前状态为:" + context.getState());

    }
}

结果:

ConcreteStateA 状态的 handle 方法

当前状态为:通用模板.ConcreteStateA@1b6d3586

ConcreteStateB 状态的 handle 方法

当前状态为:通用模板.ConcreteStateB@4554617c

具体实例

相信很多人都玩过王者荣耀这款手游,在对局中,王者荣耀里面的英雄遭受到不同的技能或者增益buff会有不同的状态,比如眩晕、加速、减速等等。如果不使用状态模式的话,我们的英雄类会非常的复杂和难以维护。此时使用状态模式才是更好的选择,下面用代码模拟实现一遍。

RunState.java

public interface RunState {

    void run(Hero hero);

}

CommonState.java、SpeedUpState.java、SpeedDownState.java、SwimState.java

public class CommonState implements RunState {
    @Override
    public void run(Hero hero) {
        // 正常跑动
    }
}


public class SpeedUpState implements RunState {
    @Override
    public void run(Hero hero) {
        System.out.println("--------------加速跑动---------------");
        try {
            Thread.sleep(4000);//假设加速持续4秒
        } catch (InterruptedException e) {}
        hero.setState(Hero.COMMON);
        System.out.println("------加速状态结束,变为正常状态------");

    }
}


public class SpeedDownState implements RunState{
    @Override
    public void run(Hero hero) {
        System.out.println("--------------减速跑动---------------");
        try {
            Thread.sleep(4000);//假设减速持续4秒
        } catch (InterruptedException e) {}
        hero.setState(Hero.COMMON);
        System.out.println("------减速状态结束,变为正常状态------");
    }
}


public class SwimState implements RunState{
    @Override
    public void run(Hero hero) {
        System.out.println("--------------不能跑动---------------");
        try {
            Thread.sleep(2000);//假设眩晕持续2秒
        } catch (InterruptedException e) {}
        hero.setState(Hero.COMMON);
        System.out.println("------眩晕状态结束,变为正常状态------");
    }
}

Hero.java

public class Hero {

    public static final RunState COMMON = new CommonState();//正常状态

    public static final RunState SPEED_UP = new SpeedUpState();//加速状态

    public static final RunState SPEED_DOWN = new SpeedDownState();//减速状态

    public static final RunState SWIM = new SwimState();//眩晕状态

    private RunState state = COMMON;//默认是正常状态

    private Thread runThread;//跑动线程

    //设置状态
    public void setState(RunState state) {
        this.state = state;
    }

    //停止跑动
    public void stopRun() {
        if (isRunning()) {
            runThread.interrupt();
        }
        System.out.println("--------------停止跑动---------------");
    }

    //开始跑动
    public void startRun() {
        if (isRunning()) {
            return;
        }
        final Hero hero = this;
        runThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!runThread.isInterrupted()) {
                    state.run(hero);
                }
            }
        });
        System.out.println("--------------开始跑动---------------");
        runThread.start();
    }

    private boolean isRunning() {
        return runThread != null && !runThread.isInterrupted();
    }

}

测试:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Hero hero = new Hero();
        hero.startRun();
        hero.setState(Hero.SPEED_UP);
        Thread.sleep(5000);
        hero.setState(Hero.SPEED_DOWN);
        Thread.sleep(5000);
        hero.setState(Hero.SWIM);
        Thread.sleep(5000);
        hero.stopRun();
    }
}

结果:

--------------开始跑动---------------

--------------加速跑动---------------

------加速状态结束,变为正常状态------

--------------减速跑动---------------

------减速状态结束,变为正常状态------

--------------不能跑动---------------

------眩晕状态结束,变为正常状态------

--------------停止跑动---------------


总结

状态模式与策略模式区别
状态模式和策略模式的 UML 一样,但是解决的问题和侧重不一样。

  1. 状态模式重点在各状态之间的切换从而做不同的事情,而策略模式更侧重于根据具体情况选择策略,并不涉及切换。

  2. 状态模式不同状态下做的事情不同,而策略模式做的都是同一件事,例如聚合支付平台,有支付宝、微信支付、银联支付,虽然策略不同,但最终做的事情都是支付,也就是说他们之间是可替换的。反观状态模式,各个状态的同一方法做的是不同的事,不能互相替换。

  3. 状态模式封装了对象的状态,而策略模式封装算法或策略。因为状态是跟对象密切相关的,它不能被重用;而通过从Context中分离出策略或算法,我们可以重用它们。

  4. 在状态模式中,每个状态通过持有Context的引用,来实现状态转移;但是每个策略都不持有Context的引用,它们只是被Context使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值