设计模式——行为型模式之借助状态模式(State Pattern)减少多状态对象本身内部使用不必要的if-else if -else和switch-case(九)

引言

前一篇文章 设计模式——行为型模式之借助策略模式减少使用不必要的if-else if -else和switch-case [草稿] 总结的是行为型模式中的策略模式,
通过策略模式我们可以把多种算法封装到一个管理类中,统一调用且可以互相交换,从而达到消除动态使用具体策略类时的条件语句,简单的可以看成是动态的选择调用已知多个对象中某一个对象。在行为型模式中还有一种模式结构和策略模式大同小异,但是是针对于某一对象根据自身的状态去完成对应的工作的,它就是状态模式。通过状态模式我们可以对象自身的不必要的条件语句。
- 设计模式——行为型之使用模板方法(Template Method Pattern)模式尽量减少重复相似的代码段(一)
- 设计模式——行为型模式之通过中介者模式(Mediator Pattern)实现各模块之间的解耦(二)
- 设计模式——行为型模式之借助策略模式(Strategy Pattern)减少使用不必要的if-else if -else和switch-case(三)
- 设计模式——行为型设计模之借助观察者模式(Observer Pattern)实现模块之间的解耦(四)
- 设计模式——行为型模式之借助责任链模式(Chain of Responsibility)灵活完成链式处理(五)
- 设计模式——行为型之命令模式(Command Pattern)让你的命令发起者和命令执行者的逻辑解耦(六)
- 设计模式——行为型之使用备忘录模式(Memento Pattern)随时回滚状态(七)
- 设计模式——行为型模式之借助策略模式(Strategy Pattern)减少使用不必要的if-else if -else和switch-case(八)
- 设计模式——行为型模式之借助状态模式(State Pattern)减少多状态对象本身内部使用不必要的if-else if -else和switch-case(九)

一、状态模式概述

状态模式(State Pattern)是一种简单的行为型模式,其官方定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类(The State Pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class)。所以这个对象又叫做状态对象(Objects for States),状态模式专注于是简化某一状态对象的状态判断逻辑,通过把状态的判断逻辑转移到表示不同状态的一系列类中从而把复杂的判断逻辑简化。核心参与角色:状态对象(又称为环境类)状态对象的抽象状态类具体的状态对象

二、状态模式的优点和缺点及可用场景

1、状态模式的优点

  • 封装了转换规则,将所有与某个状态有关的行为放到一个状态对象中且提供了一个更好的方法来组织管理与特定状态相关的代码,将繁琐的状态条件判断语句转换为结构明朗的状态环境类,保证了可读性和可扩展性。
  • 可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
  • 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
  • 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

2、状态模式的缺点

  • 状态模式的使用必然会增加系统类和对象的个数。
  • 状态模式对”开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。

3、状态模式的可用场景

状态模式的意图就是:允许一个对象在其内部状态改变时改变它的行为。简单来说当状态对象本身存在很多决定自身状态的条件分支时,而自身的行为又受条件分支的影响

  • 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
  • 一个操作中含有庞大的多条件分支结构,并且这些分支决定于对象的状态。
  • 在行为受状态约束的时候使用状态模式,而且状态不宜超过 5 个。

三、状态模式的实现

当系统中某个状态对象存在多个状态且这些状态之间可以进行转换,而且状态对象在不同状态下行为不相同时可以使用状态模式。状态模式将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化,对于客户端而言,无须关心对象状态的转换以及对象所处的当前状态,无论对于何种状态的对象,客户端都可以一致处理,通用UML类图如下
这里写图片描述

  • ContextStateHandle(环境类)又称为上下文类,它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象,定义客户端需要的接口,并且负责具体状态的切换,环境角色有两个不成文的约束:

    • 状态对象声明为静态常量,有几个状态对象就声明几个静态常量
    • 环境角色具有状态抽象角色定义的所有行为,具体执行使用委托方式
  • State(抽象状态类)它用于定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中,负责对象状态定义,并且封装环境角色以实现状态切换

  • ConcreteState(具体状态类)它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。每一个具体状态必须完成两个职责:本状态的行为管理以及趋向状态处理,简而言之,就是本状态下要做的事情,以及本状态如何过渡到其他状态

    接下来举个关于电梯的例子来实战下,豪不夸张地说网上很多所谓的状态模式的实现都只是策略模式的变种,容易误导新人,很多例子都只是把状态模式定义中的一条特性体现出来,而状态模式的定义体现的是两条特性,当初我学习的时候也是查了很多资料,直到看了秦小波老师的《设计模式之禅》关于状态模式的知识,才真正体会到策略模式和状态模式真正的区别,好了进入正题,首先简单交代下场景:电梯有四个状态——开门、关门、运行、停止,每一个状态都对应一些操作,当前状态处于开门的时候,此时能做的操作只有关门;当前状态处于关门的时候,此时能做的操作开门、运行、停止;当前状态处于运行的时候,此时能做的操作只有停止;当前状态处于停止的时候,此时能做的操作开门、运行,最后使用状态模式来实现步骤:
    这里写图片描述

1、实现State抽象类

public abstract class LiftState {
    // 定义一个环境角色,也就是封装状态的变化引起的功能变化
    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();
}

2、分别实现具体状态类

本状态下要做的事情,以及本状态如何切换到其他状态

package state3;

public class OpenningState extends LiftState {
    // 开启当然可以关闭了,我就想测试一下电梯门开关功能
    @Override
    public void close() {
        System.out.println("OpenningState开门状态下:"+"执行关闭电梯门动作...");
        // 状态修改
        super.context.setLiftState(Context.closeingState);
        // 动作委托为CloseState来执行
        super.context.getLiftState().close();
    }

    // 打开电梯门
    @Override
    public void open() {
        System.out.println("####OpenningState开门状态下:"+"执行打开电梯门...");
    }

    // 门开着时电梯就运行跑,这电梯,吓死你!
    @Override
    public void run() {
        // do nothing;
        System.out.println("OpenningState开门状态下:"+"不能执行运行操作...");
        return;
    }

    // 开门还不停止?
    public void stop() {
        // do nothing;
        System.out.println("OpenningState开门状态下:"+"不能执行停止操作...");
        return;
    }
}
package state3;

public class ClosingState extends LiftState {
    // 电梯门关闭,这是关闭状态要实现的动作
    @Override
    public void close() {
        System.out.println("####ClosingState关闭状态下:"+"执行关闭电梯门动作...");
    }

    // 电梯门关了再打开
    @Override
    public void open() {
        System.out.println("ClosingState关闭状态下:"+"为了能执行开门,先把状态改为openningState再跳到OpenningState中去执行开门");
        super.context.setLiftState(Context.openningState); // 设置为敞门状态
        super.context.getLiftState().open();
    }

    // 电梯门关了就运行,这是再正常不过了
    @Override
    public void run() {
        System.out.println("ClosingState关闭状态下:"+"为了能运行,先把状态改为RunningState再跳到RunningState中去执行运行动作");
        super.context.setLiftState(Context.runningState); // 设置为运行状态
        super.context.getLiftState().run();//实质上是通过运行状态类来执行运行操作
    }

    // 电梯门关着,我就不按楼层
    @Override
    public void stop() {
        System.out.println("ClosingState关闭状态下:"+"为了能让电梯停止,先把状态改为StoppingState再跳到StoppingState中去执行停止动作");
        super.context.setLiftState(Context.stoppingState); // 设置为停止状态
        super.context.getLiftState().stop();
    }
}
package state3;

public class RunningState extends LiftState {
    // 电梯门关闭?这是肯定的
    @Override
    public void close() {
        // do nothing
        System.out.println("RunningState状态下:"+"不能执行运行操作...");
        return;
    }

    // 运行的时候开电梯门?你疯了!电梯不会给你开的
    @Override
    public void open() {
        // do nothing
        System.out.println("RunningState状态下:"+"不能执行打开操作...");
        return;
    }

    // 这是在运行状态下要实现的方法
    @Override
    public void run() {
        System.out.println("####RunningState状态下:"+"执行运行操作...");
    }

    // 这绝对是合理的,只运行不停止还有谁敢坐这个电梯?!估计只有上帝了
    @Override
    public void stop() {
        System.out.println("RunningState状态下:"+"不能执行停止操作...");
        super.context.setLiftState(Context.stoppingState);// 环境设置为停止状态
        super.context.getLiftState().stop();
    }
}
package state3;

public class StoppingState extends LiftState {
    // 停止状态关门?电梯门本来就是关着的!
    @Override
    public void close() {
        // do nothing;
        System.out.println("StoppingState状态下:"+"不能执行关闭操作...");
        return;
    }

    // 停止状态,开门,那是要的!
    @Override
    public void open() {
        System.out.println("StoppingState状态下:"+"执行开门操作...");
        super.context.setLiftState(Context.openningState);
        super.context.getLiftState().open();
    }

    // 停止状态再运行起来,正常得很
    @Override
    public void run() {
        super.context.setLiftState(Context.runningState);
        super.context.getLiftState().run();
    }

    // 停止状态是怎么发生的呢?当然是停止方法执行了
    @Override
    public void stop() {
        System.out.println("####StoppingState状态下:"+"执行停止操作...");

    }
}

3、实现Context环境类

一般需要把所有具体状态定义出来,然后定义执行各自的动作

package state3;

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 StoppingState stoppingState = new StoppingState();
    // 定义一个当前电梯状态
    private LiftState liftState;

    public LiftState getLiftState() {
        return liftState;
    }

    public void setLiftState(LiftState liftState) {
        this.liftState = liftState;
        // 把当前的环境通知到各个实现类中
        this.liftState.setContext(this);
    }

    public void open() {
        this.liftState.open();
    }

    public void close() {
        this.liftState.close();
    }

    public void run() {
        this.liftState.run();
    }

    public void stop() {
        this.liftState.stop();
    }
}

4、测试运行

package state3;

public class LiftClient {

    /**
     *通过这个模式 你可以在任意状态下调用任意操作
     */
    public static void main(String[] args) {
        Context context = new Context();
        //电梯关门状态
        context.setLiftState(new ClosingState());//首先给Context设置当前状态为关门状态
        System.out.println("**首先给Context设置当前状态为关门状态**");
        context.open();//然后调用ClosingState里的open方法,而ClosingState里的open方法里,此时关门状态要想实现开门这个操作,必须是在开门状态的时候才能做,于是先把状态改为开门
        context.run();
        context.stop();
        context.close();//然后调用ClosingState里的open方法

    }

}

这里写图片描述

四、策略模式和状态模式区别

状态模式和策略模式的实现方法非常类似,都是利用多态把一些操作分配到一组相关的简单的类中,因此很多人认为这两种模式实际上是相同的。然而在现实世界中,策略(如促销一种商品的策略)和状态(如同一个按钮来控制一个电梯的状态,又如手机界面中一个按钮来控制手机)是两种完全不同的思想。当我们对状态和策略进行建模时,这种差异会导致完全不同的问题。例如,对状态进行建模时,状态迁移是一个核心内容;然而,在选择策略时,迁移与此毫无关系。另外,策略模式允许一个客户选择或提供一种策略,而这种思想在状态模式中完全没有。一个策略是一个计划或方案,通过执行这个计划或方案,我们可以在给定的输入条件下达到一个特定的目标。策略是一组方案,他们可以相互替换;选择一个策略,获得策略的输出。策略模式用于随不同外部环境采取不同行为的场合。我们可以参考微软企业库底层Object Builder的创建对象的strategy实现方式。而状态模式不同,对一个状态特别重要的对象,通过状态机来建模一个对象的状态;状态模式处理的核心问题是状态的迁移,因为在对象存在很多状态情况下,对各个business flow,各个状态之间跳转和迁移过程都是及其复杂的。例如一个工作流,审批一个文件,存在新建、提交、已修改、HR部门审批中、老板审批中、HR审批失败、老板审批失败等状态,涉及多个角色交互,涉及很多事件,这种情况下用状态模式(状态机)来建模更加合适;把各个状态和相应的实现步骤封装成一组简单的继承自一个接口或抽象类的类,通过另外的一个Context来操作他们之间的自动状态变换,通过event来自动实现各个状态之间的跳转。在整个生命周期中存在一个状态的迁移曲线,这个迁移曲线对客户是透明的。我们可以参考微软最新的WWF 状态机工作流实现思想。在状态模式中,状态的变迁是由对象的内部条件决定,外界只需关心其接口,不必关心其状态对象的创建和转化;而策略模式里,采取何种策略由外部条件(C)决定
他们应用场景(目的)却不一样,State模式重在强调对象内部状态的变化改变对象的行为,Strategy模式重在外部对策略的选择,策略的选择由外部条件决定,也就是说算法的动态的切换。但由于它们的结构是如此的相似,我们可以认为“状态模式是完全封装且自修改的策略模式”。即状态模式是封装对象内部的状态的,而策略模式是封装算法族的。

猜你喜欢

转载自blog.csdn.net/CrazyMo_/article/details/72980331
今日推荐