状态设计模式

状态设计模式是一种行为设计模式,它允许对象在其内部状态更改时更改其行为,就像它在运行时切换到不同的类一样。

它在以下情况下特别有用:

对象可以处于许多不同状态之一,每种状态都有不同的行为。

对象的行为取决于当前上下文,并且该上下文会随着时间的推移而变化。

你希望避免检查每个可能状态的大型、整体式或语句。if-elseswitch

当面对这种情况时,开发人员通常首先在类中使用条件逻辑来切换基于状态变量的行为。

例如,类 可能会使用 块来确定要执行的作,具体取决于它是处于"草稿"、"审阅"还是"已发布"状态。Documentif-else

但随着状态数量的增长,这种方法变得难以扩展、难以测试,并且违反了开放/封闭原则------任何新状态都需要修改现有逻辑,从而增加了破坏当前功能的风险。

状态模式通过将每个状态封装到自己的类中,并让上下文对象将行为委托给当前状态对象来解决这个问题。这使您的代码更易于扩展、重用和维护,而不会因条件而使核心逻辑变得混乱。

让我们通过一个真实世界的示例来了解如何应用状态模式以干净、可扩展和面向对象的方式管理动态行为。

问题:管理自动售货机状态

想象一下,您正在构建一个简单的自动售货机系统。从表面上看,这似乎是一项简单的任务:接受资金,分配产品,然后回到闲置状态。

但在幕后,机器的行为需要根据其当前状态而变化。

在任何给定时间,自动售货机只能处于一种状态,例如:

IdleState -- 等待用户输入(未选择任何内容,未插入任何资金)。

ItemSelectedState -- 已选择项目,等待付款。

HasMoneyState -- 已插入资金,等待分配所选项目。

DispensingState -- 机器正在主动分配项目。

该机器支持一些面向用户的作:

selectItem(String itemCode)-- 选择要购买的商品

insertCoin(double amount)-- 插入所选项目的付款

dispenseItem()-- 触发物品分配过程

这些方法中的每一种都应根据计算机的当前状态以不同的方式运行。

例如:

在 机器打开时 调用(未选择任何项目,未插入任何资金)不应执行任何作或显示错误。dispenseItem()IdleState

在选择项目之前调用可能会被禁止或排队。insertCoin()

应 忽略或推迟期间调用,直到项目被分配。selectItem()DispensingState

天真的方法

一种常见但有缺陷的方法是 使用 or 语句在整体类中手动管理状态转换:VendingMachineif-elseswitch

复制代码
public class VendingMachine {
    private enum State {
        IDLE, ITEM_SELECTED, HAS_MONEY, DISPENSING
    }

    private State currentState = State.IDLE;
    private String selectedItem = "";
    private double insertedAmount = 0.0;

    public void selectItem(String itemCode) {
        switch (currentState) {
            case IDLE:
                selectedItem = itemCode;
                currentState = State.ITEM_SELECTED;
                break;
            case ITEM_SELECTED:
                System.out.println("Item already selected");
                break;
            case HAS_MONEY:
                System.out.println("Payment already received for item");
                break;
            case DISPENSING:
                System.out.println("Currently dispensing");
                break;
        }
    }

    public void insertCoin(double amount) {
        switch (currentState) {
            case IDLE:
                System.out.println("No item selected");
                break;
            case ITEM_SELECTED:
                insertedAmount = amount;
                System.out.println("Inserted $" + amount + " for item");
                currentState = State.HAS_MONEY;
                break;
            case HAS_MONEY:
                System.out.println("Money already inserted");
                break;
            case DISPENSING:
                System.out.println("Currently dispensing");
                break;
        }
    }

    public void dispenseItem() {
        switch (currentState) {
            case IDLE:
                System.out.println("No item selected");
                break;
            case ITEM_SELECTED:
                System.out.println("Please insert coin first");
                break;
            case HAS_MONEY:
                System.out.println("Dispensing item '" + selectedItem);
                currentState = State.DISPENSING;

                // Simulate delay and completion
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }

                System.out.println("Item dispensed successfully.");
                resetMachine();
                break;
            case DISPENSING:
                System.out.println("Already dispensing. Please wait.");
                break;
        }
    }

    private void resetMachine() {
        selectedItem = "";
        insertedAmount = 0.0;
        currentState = State.IDLE;
    }
}

这种方法有什么问题?

虽然使用 with 语句适用于小型、可预测的系统,但这种方法不能很好地扩展。enumswitch

    1. 代码混乱
      所有与状态相关的逻辑都塞进一个类 () 中,导致每个方法( 、 、 等)都出现大而重复的块。VendingMachineswitchif-elseselectItem()insertCoin()dispenseItem()

这导致:

难以阅读和推理的代码

跨多个方法重复检查状态

多个开发人员接触同一文件时的逻辑脆弱

    1. 难以扩展
      假设你想引入新的状态,例如:
      OutOfStockState-- 当所选商品售罄时
      MaintenanceState-- 机器正在维修时
      要支持这些,您需要:
      更新 每个方法中的每个开关块
      在多个位置添加逻辑
      破坏现有功能的风险
      这违反了开放/封闭原则------当系统应该开放扩展时,系统可以进行修改。
    1. 违反单一责任原则
      该 班现在负责:VendingMachine
      管理状态转换
      实施业务规则
      执行特定于状态的逻辑
      这种紧密耦合使该类成为单片、难以测试且耐变化。

我们真正需要什么

我们需要将与每个状态关联的行为封装到它自己的类中------这样自动售货机就可以将工作委托给当前状态对象,而不是在内部管理它。

这将使我们能够:

避免疯狂的开关情况

添加或删除状态而不修改核心类

保持每个状态的逻辑隔离和可重用

这正是状态设计模式所实现的。

状态模式

State 模式允许对象(Context)在其内部状态更改时更改其行为。该对象似乎更改了其类,因为它的行为现在已委托给不同的状态对象。

状态模式不是将特定于状态的行为嵌入上下文类本身,而是将该行为提取到单独的类中,每个类代表一个不同的状态。上下文对象保存对状态对象的引用,并将其作委托给它。

这会产生更干净、更模块化和可扩展的代码。

定义一个 State 接口(或抽象类),该接口声明表示 Context 可以执行的作的方法。

创建 ConcreteState 类,每个类都实现 State 接口。每个 ConcreteState 类都实现特定于 Context 的特定状态的行为。

Context类维护定义其当前状态的ConcreteState子类的实例。

在 Context 上调用作时,它会将该作委托给其当前 State 对象。

ConcreteState 对象通常负责将 Context 转换为新状态。

类图

    1. 状态接口(例如MachineState)
      声明与上下文支持的作相对应的方法(例如,, ,)。selectItem()insertCoin()dispenseItem()
      这些方法通常将上下文作为参数,因此状态可以触发转换或作上下文数据。
      作为所有具体状态的共同合同。
    1. 具体状态(例如, IdleStateItemSelectedState)
      实现接口 。State
      为 每个作定义特定于状态的行为。
      通常负责 在发生特定作时将上下文转换为另一种状态。
      还可以包括特定于状态的逻辑(例如,验证、消息传递)。
    1. 上下文(例如VendingMachine)
      维护对当前对象的引用 。State
      定义每个作(、 等)的方法。selectItem()insertCoin()
      委托对当前状态的调用 --- 状态对象处理逻辑。
      提供一种方法,例如允许状态之间的转换。setState()

实现状态模式

我们将应用状态模式来分离关注点,并使自动售货机更易于管理和扩展,而不是使用 or 语句将状态转换和行为硬编码为单个整体类。if-elseswitch

第一步是定义一个 接口,用于声明自动售货机支持的所有作。MachineState

    1. 定义状态接口
      该接口表示所有州都必须遵循的契约。它声明与自动售货机面向用户的作相对应的方法。

    public interface MachineState {
    void selectItem(VendingMachine context, String itemCode);
    void insertCoin(VendingMachine context, double amount);
    void dispenseItem(VendingMachine context);
    }

每个状态都将实现此接口,定义自动售货机在该状态下的行为方式。

    1. 实现具体状态类
      每个状态类将实现 接口并为每个作定义其行为。MachineState

🟡 空闲状态

复制代码
public class IdleState implements MachineState {
    @Override
    public void selectItem(VendingMachine context, String itemCode) {
        System.out.println("Item selected: " + itemCode);
        context.setSelectedItem(itemCode);
        context.setState(new ItemSelectedState());
    }

    @Override
    public void insertCoin(VendingMachine context, double amount) {
        System.out.println("Please select an item before inserting coins.");
    }

    @Override
    public void dispenseItem(VendingMachine context) {
        System.out.println("No item selected. Nothing to dispense.");
    }
}

🟠 ItemSelected状态

复制代码
public class ItemSelectedState implements MachineState {
    @Override
    public void selectItem(VendingMachine context, String itemCode) {
        System.out.println("Item already selected: " + context.getSelectedItem());
    }

    @Override
    public void insertCoin(VendingMachine context, double amount) {
        System.out.println("Inserted $" + amount + " for item: " + context.getSelectedItem());
        context.setInsertedAmount(amount);
        context.setState(new HasMoneyState());
    }

    @Override
    public void dispenseItem(VendingMachine context) {
        System.out.println("Insert coin before dispensing.");
    }
}

🟢 有钱州

复制代码
public class HasMoneyState implements MachineState {
    @Override
    public void selectItem(VendingMachine context, String itemCode) {
        System.out.println("Cannot change item after inserting money.");
    }

    @Override
    public void insertCoin(VendingMachine context, double amount) {
        System.out.println("Money already inserted.");
    }

    @Override
    public void dispenseItem(VendingMachine context) {
        System.out.println("Dispensing item: " + context.getSelectedItem());
        context.setState(new DispensingState());

        // Simulate dispensing
        try { Thread.sleep(1000); } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        System.out.println("Item dispensed successfully.");
        context.reset();
    }
}

🔵 分配状态

复制代码
public class DispensingState implements MachineState {
    @Override
    public void selectItem(VendingMachine context, String itemCode) {
        System.out.println("Please wait, dispensing in progress.");
    }

    @Override
    public void insertCoin(VendingMachine context, double amount) {
        System.out.println("Please wait, dispensing in progress.");
    }

    @Override
    public void dispenseItem(VendingMachine context) {
        System.out.println("Already dispensing. Please wait.");
    }
}
    1. 实现上下文 (VendingMachine)
      类(我们的上下文)将维护对当前状态的引用,并将所有作委托给它。VendingMachine

    public class VendingMachine {
    private MachineState currentState;
    private String selectedItem;
    private double insertedAmount;

    复制代码
     public VendingMachine() {
         this.currentState = new IdleState(); // Initial state
     }
    
     public void setState(MachineState newState) {
         this.currentState = newState;
     }
    
     public void setSelectedItem(String itemCode) {
         this.selectedItem = itemCode;
     }
    
     public void setInsertedAmount(double amount) {
         this.insertedAmount = amount;
     }
    
     public String getSelectedItem() {
         return selectedItem;
     }
    
     public double getInsertedAmount() {
         return insertedAmount;
     }
    
     public void selectItem(String itemCode) {
         currentState.selectItem(this, itemCode);
     }
    
     public void insertCoin(double amount) {
         currentState.insertCoin(this, amount);
     }
    
     public void dispenseItem() {
         currentState.dispenseItem(this);
     }
    
     public void reset() {
         this.selectedItem = "";
         this.insertedAmount = 0.0;
         this.currentState = new IdleState();
     }

    }

客户端代码

复制代码
public class VendingMachineApp {
    public static void main(String[] args) {
        VendingMachine vm = new VendingMachine();

        vm.insertCoin(1.0); // Invalid in IdleState
        vm.selectItem("A1");
        vm.insertCoin(1.5);
        vm.dispenseItem();

        System.out.println("\n--- Second Transaction ---");
        vm.selectItem("B2");
        vm.insertCoin(2.0);
        vm.dispenseItem();
    }
}

通过使用状态模式,我们将僵化的、条件密集的实现转变为一个干净、灵活的架构,其中行为和转换被明确定义、解耦且易于维护。

相关推荐
快乐肚皮4 分钟前
IntelliJ IDEA Debug 模式功能指南
java·ide·intellij-idea·debug
阿阳微客9 分钟前
CSGO搬砖项目详解:从装备选择到市场策略
笔记·学习·游戏
RanceGru11 分钟前
神经网络学习笔记11——高效卷积神经网络架构SqueezeNet
笔记·神经网络·学习
whale fall12 分钟前
【雅思020】Opening a bank account
学习
_風箏21 分钟前
SpringBoot【ElasticSearch集成 02】Java HTTP Rest client for ElasticSearch Jest 客户端集成
java·后端·elasticsearch
慕伏白24 分钟前
【慕伏白】CTFHub 技能树学习笔记 -- Web 之密码口令
笔记·学习
野犬寒鸦35 分钟前
力扣hot100:字母异位词分组和最长连续序列(49,128)
java·数据结构·后端·算法·哈希算法
浮游本尊37 分钟前
Java学习第14天 - 微服务架构与Spring Cloud
java
燃尽余火42 分钟前
Knife4j 文档展示异常的小坑
java·开发语言·spring
17岁的勇气1 小时前
Unity Shader unity文档学习笔记(二十一):几种草体的实现方式(透明度剔除,GPU Instaning, 曲面细分+几何着色器实现)
笔记·学习·unity