首页 > 其他分享 >状态模式总结

状态模式总结

时间:2024-01-18 23:44:58浏览次数:25  
标签:总结 状态 void 模式 state context public

综述

本文总结了状态模式的定义,特点,使用场景以及实现思路。

状态模式的定义

状态模式为23种设计模式之中应用比较少见的模式,这并不是因为其适用范围过于狭窄,而是由于其兄弟模式“策略模式”实在过于强大了,但本文只关注状态模式,这里提一句以供参考。
状态模式是一种简化多步骤流程的类的模式。如果一个类提供一个具有多个步骤的流程,比如要达成目标A,需要通过步骤1,步骤2,步骤3完成。它就适合使用状态模式。
状态模式通过将多个步骤对应的方法和运行相应方法所需要的参数抽象为单独的状态,然后将状态对应的方法和参数变化分散到每个状态对应的状态实现类中,使得变更对象状态时直接变更负责的状态实现类,从而不再需要修改对象的行为,简化条件判断语句,提高代码可读性和可维护性,减少逻辑错漏。

状态模式的特点和使用场合

状态模式具有如下优点:

结构清晰、封装性好:将状态的转换逻辑分布到独立的状态类中,使得状态之间的耦合度降低,并且可以将状态的行为封装在状态类中,提高了系统的可维护性和可读性。
易于维护和调试:状态模式将各个状态进行了封装,每个状态对象都只关注自身的行为,使得代码易于维护和调试。

但是状态模式也存在一些缺点:

状态模式会导致系统中类和对象的个数增加:状态模式将每个状态都封装成了独立的对象,因此会增加系统的复杂度。
状态模式违背开闭原则:由于状态模式将多个状态视为一个整体,状态之间的切换逻辑封装在内部,使得其在需要变更流程的时候必须修改原有的代码,违背了开闭原则。这也是它不如“策略模式”受欢迎的原因。

状态模式的实现思路:

下图为状态模式的示意图

根据 GoF 的定义,状态模式的三个核心角色分别是:

上下文(Context):在有的地方又被翻译为环境,它定义了调用者所需要的方法,在任意时间点维护一个流程当前的状态,并将不同具体状态类中的方法封装统一对外提供。
抽象状态(State):它定义了一个接口,用于封装上下文对象中不同状态对应的行为。有的变种版本会选择使用抽象类来实现抽象状态。
具体状态(Concrete State):即前文所指的状态实现类,它实现了抽象状态接口或作为抽象类的实现类,封装了不同状态中上下文对象的具体行为。

具体实现时可以按照以下步骤实现

  1. 定义抽象状态接口或者实现类(State),它定义了在流程中上下文在每个具体状态下应该具有的行为。
  2. 定义具体状态类(ConcreteState1、ConcreteState2等),它们实现了抽象状态接口,封装了具体的状态行为。
  3. 定义上下文类/环境类(Context),它包含了当前状态并将不同状态下的行为封装成统一的方法对外提供。
  4. 让上下文类持有抽象状态的类型的对象,并将抽象状态的方法封装成统一的方法对外提供调用。

实现状态模式的最小示例

下面展示一个实现状态模式的最小示例, 如果从精炼角度出发可能还能继续精炼, 但是当前已经足够帮助理解.

首先是一个对外提供服务的StateContext, 其他的类可以看作是context的内部组件

public class StateContext {

    private State state;
//    private StateFlag stateFlag;

    public StateContext(){
        this.state = new StepOneStateImpl();
        this.state.setContext(this);
    }
    public StateContext(StateFlag flag) {
        if (flag == StateFlag.NOT_START) {
            this.state = new StepOneStateImpl();
            this.state.setContext(this);
        }
        if (flag.equals(StateFlag.FINISHED_STEP_ONE)){
            this.state = new StepTwoStateImpl();
            this.state.setContext(this);
        }
    }

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

    public StateFlag getCurrentState(){
        if (state != null) {
            return state.getState();
        }
        throw new IllegalStateException("当前错误的处于未定义状态");
    }

    public void nextStep() {
        state.doSomething();
    }
}

然后是State接口以及配合的AbstractState抽象类, 两者各有优势, 所以示例中同时用了

public interface State {

    void setContext(StateContext context);

    void doSomething();

    StateFlag getState();

}

public abstract class AbstractState implements State{

    protected StateContext context;

    @Override
    public void setContext(StateContext context){
        this.context = context;
    }

    @Override
    public abstract void doSomething();

    @Override
    public abstract StateFlag getState();
}

然后是标记状态的枚举类, 这里为了让其同步输出log将枚举类写得功能多一些, 实际使用可以简化后放在context中作为内部类

public enum StateFlag {
    NOT_START("Not Start"),
    FINISHED_STEP_ONE("Finished Step One");

    private final String stateString;

    StateFlag(String s) {
        this.stateString = s;
    }

    public String getStateString(){
        return stateString;
    }
}

接着是具体实现状态下行为的ConcreteState类, 或者称为状态行为类

public class StepOneStateImpl extends AbstractState{

    public static final StateFlag state = StateFlag.NOT_START;

    @Override
    public void doSomething() {
        System.out.println("执行第一步");
        if (context == null){
            throw new RuntimeException("初始化state时未设置stateImpl中的context实例");
        }
        StepTwoStateImpl stepTwoState = new StepTwoStateImpl();
        stepTwoState.setContext(context);
        context.setState(stepTwoState);

    }

    @Override
    public StateFlag getState() {
        return state;
    }
}

public class StepTwoStateImpl extends AbstractState {

    public static final StateFlag state = StateFlag.FINISHED_STEP_ONE;
    @Override
    public void doSomething() {
        System.out.println("执行第二步");
        //切换回原来的状态
        StepOneStateImpl stepOneState = new StepOneStateImpl();
        stepOneState.setContext(context);
        context.setState(stepOneState);
    }

    @Override
    public StateFlag getState() {
        return state;
    }
}

最后, 如果需要调用这个模式进行测试的话在Main方法中调用即可

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

        System.out.print("Hello and welcome!\n");

        StateContext stateContext = new StateContext();
        System.out.println("未执行nextStep的状态:" + stateContext.getCurrentState().getStateString());
        stateContext.nextStep();
        System.out.println("执行1次nextStep后的状态: " + stateContext.getCurrentState().getStateString());
        stateContext.nextStep();
        System.out.println("执行2次nextStep后的状态: " + stateContext.getCurrentState().getStateString());
    }

}

状态模式拓展

前面已经提到, 状态模式属于违背开闭原则这个设计模式的总原则的模式, 同时状态模式还可以部分的被策略模式替代. 在种种不利的前提下, GoF仍然选择将状态模式加入23种设计模式中加以介绍. 我认为状态模式无法被替代的那部分功用一定相当重要使得GoF决定对其进行保留.

那么仔细思考, 状态模式之所以违背开闭原则, 核心点在于其用来实现不同状态下表现的ConcreteState类之间的切换遵守严格的切换顺序, 而且这个切换顺序无法任意定制, 也无法从ConcreteState类之间独立出来, 因为这个切换顺序是context想要表现的这个类的内置属性而不是可以从外部接收的属性. 这就导致ConcreteState类和context类以及state接口之间的耦合是比较强的.

那么鉴于这是其基本特性, 而且状态模式本来就无法完全遵循开闭原则, 所以考虑舍弃兼顾开闭原则的思路转而放大其特性. 同时尽量按照易用性和易读性进行优化.

首先拓展版本也需要准备一个extStateContext

public class ExtStateContext {
    //当前state
    private ExtState currentConcreteState;
    private ExtState.ExtStateFLag currentStateFlag;
    //所有state
    private final ExtState concreteStateOne;
    private final ExtState concreteStateTwo;

    public ExtStateContext() {
        this.concreteStateOne = new ExtConcreteStateOne();
        this.concreteStateTwo = new ExtConcreteStateTwo();
        this.setCurrentConcreteState(concreteStateOne);
    }

    public void stepOneExecute(){
        this.currentConcreteState.stepOneExecute(() -> this.setCurrentConcreteState(concreteStateTwo));
    }

    public void stepTwoExecute(){
        this.currentConcreteState.stepTwoExecute(() -> this.setCurrentConcreteState(concreteStateOne));
    }

    public void resetFlow(){
        this.currentConcreteState.resetFlow(() -> this.setCurrentConcreteState(concreteStateOne));
    }

    public ExtState.ExtStateFLag getCurrentState(){
        return this.currentStateFlag;
    }

    private void setCurrentConcreteState(ExtState concreteState){
        this.currentConcreteState = concreteState;
        this.currentStateFlag = concreteState.getState();
    }
}

然后准备接口

public interface ExtState {

    void stepOneExecute(Callback callback);

    void stepTwoExecute(Callback callback);

    void resetFlow(Callback callback);

    ExtState.ExtStateFLag getState();

    enum ExtStateFLag{
        FLOW_NOT_START("flow not start"),
        FLOW_STEP_ONE_FINISHED("flow step one finished");

        private final String stateString;

        ExtStateFLag(String stateString) {
            this.stateString = stateString;
        }

        public String getStateString() {
            return stateString;
        }
    }

    interface Callback{
        void onCallback();
    }
}

然后是两个具体实现状态下行为的ExtConcreteState实现类

public class ExtConcreteStateOne implements ExtState{
    //存放静态的状态flag
    private static final ExtState.ExtStateFLag extStateFLag = ExtStateFLag.FLOW_NOT_START;

    @Override
    public void stepOneExecute(Callback callback) {
        System.out.println("ExtConcreteStateOne, 执行第一步");
        callback.onCallback();
    }

    @Override
    public void stepTwoExecute(Callback callback) {
        throw new IllegalStateException("当前处于第一步尚未执行的状态, 无法执行第二步");
    }

    @Override
    public void resetFlow(Callback callback) {
        System.out.println("ExtConcreteStateOne, 本身处于第一步尚未执行状态, 所以不重置, 甚至不切换");
    }

    @Override
    public ExtState.ExtStateFLag getState() {
        return extStateFLag;
    }
}

public class ExtConcreteStateTwo implements ExtState{
    //存放静态的状态flag
    private static final ExtState.ExtStateFLag extStateFLag = ExtStateFLag.FLOW_STEP_ONE_FINISHED;
    @Override
    public void stepOneExecute(Callback callback) {
        throw new IllegalStateException("当前处于第一步已执行第二步尚未执行的状态, 无法重复执行第一步");
    }

    @Override
    public void stepTwoExecute(Callback callback) {
        System.out.println("ExtConcreteStateTwo, 执行第二步");
        callback.onCallback();
    }

    @Override
    public void resetFlow(Callback callback) {
        System.out.println("ExtConcreteStateTwo, 执行流程复位");
        callback.onCallback();
    }

    @Override
    public ExtStateFLag getState() {
        return extStateFLag;
    }
}

最后, 给出测试用的方法

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

        System.out.print("Hello and welcome!\n");

        ExtStateContext extStateContext = new ExtStateContext();
        System.out.println("未执行flow的状态:" + extStateContext.getCurrentState().getStateString());
        extStateContext.stepOneExecute();
        System.out.println("执行flow第一步的状态:" + extStateContext.getCurrentState().getStateString());
        extStateContext.stepTwoExecute();
        System.out.println("执行flow第二步的状态:" + extStateContext.getCurrentState().getStateString());
        extStateContext.stepOneExecute();
        extStateContext.resetFlow();
        System.out.println("执行flow第一步后执行reset的状态:" + extStateContext.getCurrentState().getStateString());
    }

}

总结

在拓展的变种模式中, 通过拆分每一步的方法, 并引入回调方法, 将状态转换操作集中到一个类中, 避免了context和concreteState之间的互相依赖, 提高代码的可读性和可维护性. 虽然在开闭原则上反而是后退了, 但是其他方面的优化盖过了这一点. 个人以为是更好的做法.

标签:总结,状态,void,模式,state,context,public
From: https://www.cnblogs.com/dwcg/p/17943548

相关文章

  • 1.18每日总结
    Python3数据类型转换有时候,我们需要对数据内置的类型进行转换,数据类型的转换,一般情况下你只需要将数据类型作为函数名即可。Python数据类型转换可以分为两种:隐式类型转换-自动完成显式类型转换-需要使用类型函数来转换隐式类型转换在隐式类型转换中,Python会自动将......
  • 《微服务架构设计模式》PDF
    成功地开发基于微服务架构的应用软件,需要掌握一系列全新的架构思想和实践。在这本独特的书籍中,微服务架构的先驱、Java开发者社区的意见领袖ChrisRichardson收集、分类并解释了44个架构设计模式,这些模式用来解决诸如服务拆分、事务管理、查询和跨服务通信等难题。本书将教......
  • 一月题目总结
    P6109rqrmq1https://www.luogu.com.cn/problem/P6109这个题有很多精妙,经典的操作。非常精彩。首先第一个经典的是,遇到二维平面就考虑扫描线。然后变成一段时间内的最大值问题后,就很自然的想到,用猫树的思想,把一段时间拆成前后缀,从而变成历史问题。关于这类,离线后维护另一个可......
  • 2024-01-17 训练总结
    A组大佬太强了不敢发,就发博客上了。T1排水系统[NOIP2020]排水系统题目描述对于一个城市来说,排水系统是极其重要的一个部分。有一天,小C拿到了某座城市排水系统的设计图。排水系统由\(n\)个排水结点(它们从\(1\simn\)编号)和若干个单向排水管道构成。每一个排水结点有......
  • # [题目总结] [COCI2015-2016#2] SAVEZ
    [题目总结][COCI2015-2016#2]SAVEZ题目题目让我们判断\(s_i\)是否是\(s_j\)的开头结尾。首先想到字符串哈希,这样仍然不优美,暴力判断点对是\(O(n^2)\)的。如果这个时候卡住了,不妨往其他方面想想。看到前缀,我们自然地想到Trie。那么这道题就做完一半了。注意题目求的是......
  • 设计模式:六大原则
    文章目录1.单一职责原则(SingleResponsibilityPrinciple,SRP)1.1.定义1.2.代码实现1.3.优缺点1.4.使用场景2.开闭原则(Open-ClosedPrinciple,OCP)2.1.定义2.2.代码实现2.3.优缺点2.4.使用场景3.里氏代换原则(LiskovSubstitutionPrinciple,LSP)3.1.定义3.2.......
  • 我所关注的几个spring设计模式
    Spring框架中实现了许多的设计模式,而且都是非常优先的实现,这些值得我们学好好习。不过话说回来,我觉得自己只要关注几个即可:单例工厂代理适配器观察者委派在各种设计模式中,适配器和装饰器、代理模式其实都很类似,只是侧重点不同而已。spring的设计模式应用的很好,但spring......
  • vue3总结
    一、OptionsAPI(选项式)和 CompositionAPI(组合式)    结合hooks:提供了一种在函数组件中共享逻辑和状态的方式。(契合组合式api)例: 1.新建hooks文件夹,存放多个处理文件(js、ts)import{onMounted,reactive}from"vue";importaxiosfrom"axios";exportdef......
  • 人性铁律,人性总结
    人性铁律:1.亲不过父母,近不过夫妻。2.除了父母,没有人关心你快不快乐,所有人都在看你有没有用。3.一代亲,二代表,三代四代就拉倒。4.真姑妈,假舅妈,半真半假是姨妈。5.所有的亲戚里面,“舅舅”才是最靠谱的。6.姐妹再好,吃亏就吵。7.兄弟再好,借钱就恼。8.人与人的关系本质,只是利益......
  • Spring ResourceLoader 总结
    ResourceLoader总结Spring将采用和ApplicationContext相同的策略来访问资源。也就是说,如果ApplicationContext是FileSystemXmlApplicationContext,res就是FileSystemResource实例;如果ApplicationContext是ClassPathXmlApplicationContext,res就是ClassPathResource实例当Spring应......