目 录CONTENT

文章目录

状态模式

半糖
2024-08-30 / 0 评论 / 0 点赞 / 8 阅读 / 13546 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2024-08-30,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

名称

状态模式(STATE),别名:状态对象(Objects for States)

目的

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

适用性

在下面的两种情况下均可使用State模式:

  • 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。

  • 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常,有多个操作包含这一相同的条件结构。State模式将每一个条件分支放入一个独立的类中,这样就可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。

结构

上下文(Context,环境)

  • 定义客户感兴趣的接口。

  • 维护一个ConcreteState子类的实例,这个实例定义当前状态。

状态(State):定义一个接口以封装与Context的一个特定状态相关的行为。

具体状态子类(ConcreteStatesubclasses):每一子类实现一个与Context的一个状态相关的行为。

协作

  1. Context将与状态相关的请求委托给当前的ConcreteState对象处理。

  2. Context可将自身作为一个参数传递给处理该请求的状态对象。这使得状态对象在必要时可访问Context。

  3. Context是客户使用的主要接口。客户可用状态对象来配置一个Context,一旦一个Context配置完毕,它的客户不再需要直接与状态对象打交道。

  4. Context或ConcreteState子类都可决定哪个状态是另外哪一个的后继者,以及是在何种条件下进行状态转换。

效果

优点

  1. 将与特定状态相关的行为局部化,并且将不同状态的行为分割开来。根据状态决定具体行为,这样做带来的好处是减少了Context中庞大的if…else和case等条件语句(这些条件语句在增加一个状态的时候可能要改变若干个操作,使得维护变得困难),坏处就是将不同的行为分布在多个子类中,子类的数目会增加

  2. 使得状态转换显式化。当一个对象仅以内部数据值来定义当前状态时,其状态仅表现为对一些变量的赋值,这不够明确。为不同的状态引入独立的对象使得转换变得更加明确。而且,State对象可保证Context不会发生内部状态不一致的情况,因为从Context的角度看,状态转换是原子的—只需重新绑定一个变量(即Context的State对象变量),而无需为多个变量赋值。
    释义:当一个对象以内部的多个变量来定义它的状态,改变状态也就需要改变多个数值,这样做一是状态不够明确,而是改变多个数值不是原子操作,如果改变了变量a刚要改变变量b的时候出现了不可预知的错误而导致变量b没有改变成,此时就会出现内部状态不一致的情况。作者的意思是如果使用ConcreteState就能避免这种问题,因为这样相当于把二维状态坍缩为一维状态。
    打个比方,如果我们要设计一款直播软件,每场直播都会有个直播状态(未开播、直播中、已结束),直播完成后会去生成回放有个回放状态(null、不生成、未生成、已生成),直播过程中的状态是直播状态为直播中,回放状态为null,直播结束后如果要生成回放,直播状态修改为已结束,回放状态修改为未生成,如果在修改回放状态的时候服务挂掉了,那么重启后从数据库中获取的状态就是直播状态已结束,回放状态为null,细想一下,有这种状态吗?这种状态是不合理的,所以就可以把直播状态和回放状态组合一下,两两组合为一个状态,那就是3*4=12个状态,实际上应该比12少(因为有些状态不会出现),这就是把二维状态转化为一维状态,而使用状态模式相当于强制限制了只能使用一维状态,因为状态模式下的Context内部只有一个状态变量,多个的话就要使用条件语句了,没有意义。

  3. State对象可被共享。State的实例可以使用享元模式让所有Context内部的State引用共享同一批State对象,此时的State对象必然是没有内部状态只有行为的轻量级对象。

  4. 易于扩展。当需要添加新的状态或改变现有状态的行为时,可以通过添加新的状态类或者修改状态子类来实现,而不需要修改现有的类。

  5. 符合开放封闭原则。新增状态时只需要添加新的状态类,而不需要修改现有代码,这符合软件设计中的开放封闭原则。

  6. 提高可维护性。将状态转换和状态行为封装在独立的状态类中,有助于提高代码的可维护性和可扩展性。

缺点

  1. 增加系统复杂度。引入了大量的状态类,可能导致系统中类和对象的数量显著增加,增加了系统的复杂度。

  2. 实现复杂。状态模式的实现相对复杂,如果使用不当,可能会导致程序结构和代码混乱,增加系统的设计难度

  3. 可能的性能问题。由于状态模式涉及到多个对象之间的交互,可能会对性能产生一定影响,尤其是在状态转换频繁的情况下。

实现

  1. 确定谁来定义状态的转换。状态模式没有确定谁来定义状态转换,可以在Context中实现,也可以在ConcreteState中实现。通常在ConcreteState中实现更加灵活也更合适,因为这种方式可以指定该状态的后继状态,使状态拥有流式转换的特性(就像订单状态由未支付转换为已支付,从已支付转化为已退款),但是这种实现需要Context提供一个让State改变其状态的接口。这种转换方式分散了转换逻辑,带来的好处就是可以很容易地定义新的State子类来修改和扩展该逻辑,当然,万物都有两面性,这样做就让一个State子类至少拥有了一个其他State子类的信息,这就在各子类间产生了羁绊(实现依赖),增加了耦合性。

  2. 基于表实现。使用表将输入映射到状态转换,对于每一个状态,一张表将每一个可能的输入映射到一个后继状态,这种方法将条件代码(包括State模式下的虚函数)映射为了一个查找表。这样做的好处是可以更改数据(而不是程序代码)来改变状态转换的准则,带来的问题(最大的问题)是难以加入伴随状态转化的一些动作,因为使用表驱动着重于定义状态转换而不是状态相关的行为。

  3. 确定如何创建State对象,是否需要销毁。当上下文不经常改变状态或者State对象信息量大时,可以在需要时创建并随后销毁,如果上下文经常改变状态或者State对象信息量不大时,可以提前创建并不销毁。什么时候创建,什么时候销毁,是否需要销毁,这些问题看情况而定。

  4. 使用动态继承。改变一个响应特定请求的行为可以用在运行时刻改变这个对象的类的办法实现,但这在大多数面向对象程序设计语言中都是不支持的。Self和其他一些基于委托的语言却是例外,它们提供这种机制,从而直接支持State模式。Self中的对象可将操作委托给其他对象以达到某种形式的动态继承。在运行时刻改变委托的目标有效地改变了继承的结构。这一机制允许对象改变它们的行为,也就是改变它们的类。

应用

问题

当我们去住酒店的时候,通常会有预订、退订、入住、退房这几个操作,这几个操作和房间状态是紧密相关的,首先预订的前提就是得有房间空闲,退订的前提是有预订房间,入住的前提是有预订房间,退房的前提是有入住,他们和空闲、已预订、已入住这几个状态紧密相关,所以我们可以用状态模式来模拟这些操作。不关注已退房状态因为这个状态和这几个操作关系不大。

首先定义房间状态State作为抽象状态,State中定义了预订、退订、入住、退房几个方法作为缺省方法;然后再定义State的子类空闲状态、已预订状态、已入住状态作为具体状态,他们分别实现各自状态需要自定义的方法(相当于if),没有自定义的方法在执行时走的是State的缺省方法(相当于else),具体状态内维护一个房间的引用,这样做的目的是可以在自定义方法内指定该状态的后继状态;最后定义一个房间类Room,内部包含一个State用于调用具体状态内的方法,重新定义预订等方法(不是重写也不是重载,而是为了间接调用具体状态内的方法),重写无参构造方法,初始化的时候实例化房间的三个状态用于状态转换时复用,实例化时传入this是把该Room对象和各具体状态内的Room引用进行绑定,这样方便指定后继状态,初始化最后一步指定房间初始状态为空闲。

示例

UML

代码示例

状态:房间状态,定义了预订、退订、入住、退房几个操作,使用抽象类而不是接口,目的是为了定义缺省方法,这样当某具体状态没有定义某方法时,执行的是父类的方法,类似于if…else中的else,if就是具体状态。

package com.ysj.part5.state.state;

/**
 * 状态:房间状态
 */
public abstract class State {

    /**
     * 预订
     */
    public void bookRoom() {
        System.out.println("当前房间状态" + this.getClass().getSimpleName() + "不可预订");
    }


    /**
     * 退订
     */
    public void unBookRoom() {
        System.out.println("当前房间状态" + this.getClass().getSimpleName() + "不可退订");
    }


    /**
     * 入住
     */
    public void checkInRoom() {
        System.out.println("当前房间状态" + this.getClass().getSimpleName() + "不可入住");
    }


    /**
     * 退房
     */
    public void checkOutRoom() {
        System.out.println("当前房间状态" + this.getClass().getSimpleName() + "不可退房");
    }

}

具体状态:空闲、已预订、已入住,只需要重写各自状态关心的操作

package com.ysj.part5.state.concreteState;

import com.ysj.part5.state.context.Room;
import com.ysj.part5.state.state.State;

/**
 * 具体状态:空闲
 */
public class FreeTimeState extends State {

    /**
     * 房间
     */
    Room room;

    public FreeTimeState(Room room) {
        this.room = room;
    }

    public void bookRoom() {
        System.out.println("您已经成功预订了...");
        // 房间状态设为已预订
        room.setState(room.getBookedState());
    }

}
package com.ysj.part5.state.concreteState;

import com.ysj.part5.state.context.Room;
import com.ysj.part5.state.state.State;

/**
 * 具体状态:已预订
 */
public class BookedState extends State {

    /**
     * 房间
     */
    Room room;

    public BookedState(Room room) {
        this.room = room;
    }

    @Override
    public void unBookRoom() {
        System.out.println("退订成功,欢迎下次光临...");
        // 状态设为空闲
        room.setState(room.getFreeTimeState());
    }

    @Override
    public void checkInRoom() {
        System.out.println("入住成功...");
        // 房间状态设为已入住
        room.setState(room.getCheckInState());
    }

}
package com.ysj.part5.state.concreteState;

import com.ysj.part5.state.context.Room;
import com.ysj.part5.state.state.State;

/**
 * 具体状态:已入住
 */
public class CheckInState extends State {

    /**
     * 房间
     */
    Room room;

    public CheckInState(Room room) {
        this.room = room;
    }

    public void checkOutRoom() {
        System.out.println("退房成功....");
        // 房间状态设为空闲
        room.setState(room.getFreeTimeState());
    }

}

上下文:房间

package com.ysj.part5.state.context;

import com.ysj.part5.state.concreteState.BookedState;
import com.ysj.part5.state.concreteState.CheckInState;
import com.ysj.part5.state.concreteState.FreeTimeState;
import com.ysj.part5.state.state.State;
import lombok.Getter;
import lombok.Setter;

/**
 * 上下文:房间
 */
@Setter
@Getter
public class Room {
    /*
     * 房间的三个状态
     */
    State freeTimeState;    //空闲状态
    State checkInState;     //入住状态
    State bookedState;      //预订状态

    State state;

    public Room() {
        /**
         * this解析:使用this:哪个来调用this所在的方法,this就代表哪个对象
         *  此处是对象调用的是room对象,所以指代room
         */
        freeTimeState = new FreeTimeState(this);
        checkInState = new CheckInState(this);
        bookedState = new BookedState(this);

        state = freeTimeState;  //初始状态为空闲
    }

    /**
     * @return void
     * @desc 预订房间
     */
    public void bookRoom() {
        state.bookRoom();
    }

    /**
     * @return void
     * @desc 退订房间
     */
    public void unBookRoom() {
        state.unBookRoom();
    }

    /**
     * @return void
     * @desc 入住
     */
    public void checkInRoom() {
        state.checkInRoom();
    }

    /**
     * @return void
     * @desc 退房
     */
    public void checkOutRoom() {
        state.checkOutRoom();
    }

}

客户端

package com.ysj.part5.state;

import com.ysj.part5.state.context.Room;

/**
 * 状态模式
 * 允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类
 * 示意图:/doc/diagram/状态模式示意图.png
 */

public class Client {

    public static void main(String[] args) {
        //有3间房
        Room[] rooms = new Room[2];
        //初始化
        for (int i = 0; i < rooms.length; i++) {
            rooms[i] = new Room();
        }
        //第一间房
        rooms[0].bookRoom();    // 预订
        rooms[0].bookRoom();    // 预定后再预定
        rooms[0].checkInRoom();   // 入住
        rooms[0].bookRoom();    // 入住后再预定
        System.out.println("打印房间状态:" + rooms[0].getState().getClass().getSimpleName());   // 打印房间状态
        System.out.println("---------------------------");

        //第二间房
        rooms[1].checkInRoom();
        rooms[1].bookRoom();
        rooms[1].checkOutRoom();
        rooms[1].bookRoom();
        System.out.println("打印房间状态:" + rooms[1].getState().getClass().getSimpleName());   // 打印房间状态
    }

}

已知应用

Spring State Machine

Spring State Machine 是 Spring 框架的一个扩展模块,用于实现有限状态机(FSM)。它支持复杂的业务流程管理和状态转换。支持多种事件触发机制,提供了丰富的配置选项,如状态动作、事件监听器等。可以与 Spring 应用无缝集成。

JState

JState 是一个基于 Java 的状态机库,专门用于实现状态模式。简单易用,提供了清晰的状态转换机制。支持动态状态图配置。

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区