状态模式在项目中的应用

1,869 阅读6分钟

这是我参与更文挑战的第10天,活动详情查看: 更文挑战

状态模式我们在实际开发中也能经常接触到,状态模式将一个对象在不同状态下的不同行为封装在一个个状态类中,通过设置不同的状态对象可以让环境对象拥有不同的行为,而状态转换的细节对于客户端而言是透明的,方便了客户端的使用。在实际开发中,状态模式具有较高的使用频率,在工作流逻辑中,在订单状态流转逻辑中有着广泛的使用。 000029_QhZV_2003960

角色划分

在状态模式结构图中包含如下几个角色:

  1. Context(环境类):环境类又称为上下文类,它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。

  2. State(抽象状态类):它用于定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中。

  3. ConcreteState(具体状态类):它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。

使用场景

我们可以简单的把使用状态模式的场景分为两个情况:

  1. 对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为。
  2. 代码中包含大量与对象状态有关的条件语句,例如,一个操作中含有庞大的分支语句 (if-else / switch-case) , 且这些分支依赖于该对象的状态。

优缺点

  • 结构清晰,避免了过多的switch…case或if…else语句的使用。
  • 很好的体现了开闭原则和单一职责原则,想要增加状态就增加子类,想要修改状态就修改子类即可。
  • 封装性非常好,状态变化放置到了类的内部来实现,外部调用不需要知道类内部如何实现状态和行为的变换。
  • 也是会出现使用设计模式的通病,就是类会增多增加维护成本。

代码示例

我们用程序来实现一个简单的开车停车流程类,开车停车有如下动作:开车门、关车门、行驶汽车、停止汽车。我们先来个图看一下: 状态模式

我们用最简单的代码写一下:

  1. 接口
public interface ICar {
    void open();
    void close();
    void run();
    void stop();
}

  1. 实现
public class Car1 implements ICar {
    @Override
    public void open() {
        System.out.println("开车门...");
    }

    @Override
    public void close() {
        System.out.println("关车门...");
    }

    @Override
    public void run() {
        System.out.println("汽车形式...");
    }

    @Override
    public void stop() {
        System.out.println("停车...");
    }
}
  1. 调用
public class Client {
    public static void main(String[] args) {
        ICar lift = new Car1();
        lift.open();
        lift.close();
        lift.run();
        lift.stop();
    }
}

上面的代码非常简单,就不过多的解释了。我们基于上面的几个步骤增加一下前置条件,也就是在什么情况下才能开车门,什么情况下才能驾驶等逻辑。 iShot2021-06-1016 如上图,比如此时是开门状态那么它下个状态只能是关门,不能是驾驶状态,也就是开门状态不能开车。其它的情况大家自己按照如图所以理解即可。如果按照这个逻辑用以上的简单形式代码编写,就会出现大量的if-else进行判断了。简单写一下:

public class Car2 implements ICar{
    private String state;
    public void setState(String state) {
        this.state = state;
    }
    //关闭
    public void close() {
        //汽车关门状态下能进行其它的什么状态
        switch(this.state) {
            case "开门":
                System.out.println("可以开门");
                break;
            case "关门":
                System.out.println("不可以关门");
                break;
            case "驾驶":
                System.out.println("可以驾驶");
                break;
            case "停车":
                System.out.println("可以停车");
                break;
        }
    }
    //汽车开门
    public void open() {
        //电梯在什么状态才能开启
        switch(this.state){
        }
    }
    //汽车驾驶
    public void run() {
        switch(this.state){
        }
    }
    //汽车停止
    public void stop() {
        switch(this.state){
        }
    }

}

随着项目的发展,这个问题会变得越来越大。在设计阶段很难预测所有可能的状态和转换。因此,随着时间的推移,这个类可能会变得臃肿。

接下来我们用状态模式进行改造一下: 按照上面我们描述的角色我们进行代码实现。

  1. 定义一个抽象状态类
public abstract class ACar {
    protected Context context;
    public void setContext(Context _context){
        this.context = _context;
    }
    public abstract void open();
    public abstract void close();
    public abstract void run();
    public abstract void stop();

}
  1. 对应4个状态具体实现
public class OpenningState extends ACar {
    @Override
    public void open() {
        System.out.println("已经开门");
    }

    @Override
    public void close() {
        System.out.println("可以进行关门");

        //关门后将环境设置成已经关门了,可以进行走关门之后的逻辑。既可以进行驾驶或者停车
        super.context.setState(Context.closeingState);

        super.context.getState().close();//进行关门

    }

    @Override
    public void run() {
        System.out.println("不能驾驶");
    }

    @Override
    public void stop() {
        System.out.println("不能停车");
    }
}


public class RunningState extends ACar {
    @Override
    public void open() {
        System.out.println("不能开门");
    }

    @Override
    public void close() {
        System.out.println("不能关门");
    }

    @Override
    public void run() {
        System.out.println("已经在驾驶");
    }

    @Override
    public void stop() {
        System.out.println("可以停车");
    }
}

public class ClosingState extends ACar {
    @Override
    public void open() {
        System.out.println("可以开门");
    }

    @Override
    public void close() {
        System.out.println("已经关门");
    }

    @Override
    public void run() {
        System.out.println("可以进行驾驶");
    }

    @Override
    public void stop() {
        System.out.println("可以进行停车");
    }
}

public class StopState extends ACar {
    @Override
    public void open() {
        System.out.println("可以进行开门");
    }

    @Override
    public void close() {
        System.out.println("不能关门");
    }

    @Override
    public void run() {
        System.out.println("可以驾驶");
    }

    @Override
    public void stop() {
        System.out.println("已经停车");
    }
}
  1. 定义一个环境类
public class Context {
    public final static OpenningState openningState = new OpenningState();
    public final static ClosingState closeingState = new ClosingState();
    public final static RunningState runningState = new RunningState();
    public final static StopState stoppingState = new StopState();
    //定义一个当前车状态
    private ACar state;
    public ACar getState() {
        return state;
    }
    public void setState(ACar state) {
        this.state = state;
        //把当前的环境通知到各个实现类中
        this.state.setContext(this);
    }
    public void open(){
        this.state.open();
    }
    public void close(){
        this.state.close();
    }
    public void run(){
        this.state.run();
    }
    public void stop(){
        this.state.stop();
    }

}
  1. 客户端测试
public class Client {
    public static void main(String[] args) {

        Context context = new Context();
        context.setState(new OpenningState());
        context.open();
        context.close();
        context.run();
        context.stop();
    }
}
  1. 输出结果

已经开门
可以进行关门
已经关门
可以进行驾驶
可以进行停车

如果不用状态模式实现的话,常用的方法是在每个方法中使用switch语句来判断当前状态进行处理,而使用状态模式,通过各个子类来实现,避免了switch语句的判断,使得代码看起来不是那么的冗杂。那么结合状态模式的特点,如果现在要增加一个状态比如加油状态呢?其实很简单,只需增加一个子类,并在原有类上增加而不是去修改,符合开闭原则;而这里我们的状态都是单独的类,只有与这个状态有关的因素修改了,这个类才修改,符合迪米特法则。