状态设计模式

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

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

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

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

你希望避免检查每个可能状态的大型、整体式或语句。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();
    }
}

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

相关推荐
考虑考虑1 天前
Jpa使用union all
java·spring boot·后端
用户3721574261351 天前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊1 天前
Java学习第22天 - 云原生与容器化
java
渣哥1 天前
原来 Java 里线程安全集合有这么多种
java
间彧1 天前
Spring Boot集成Spring Security完整指南
java
间彧1 天前
Spring Secutiy基本原理及工作流程
java
数据智能老司机1 天前
精通 Python 设计模式——创建型设计模式
python·设计模式·架构
Java水解1 天前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
数据智能老司机1 天前
精通 Python 设计模式——SOLID 原则
python·设计模式·架构
洛小豆1 天前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试