JavaScript设计模式「基于ES2024」:行为型模式-状态模式

70 阅读2分钟

状态模式允许一个对象在其内部状态改变时改变它的行为。这种模式将状态行为封装在独立的类中,并将动作委托给当前状态对象,从而消除了许多条件语句。

// 状态接口
class VendingMachineState {
    insertCoin(machine) {
        throw new Error('Method not implemented');
    }

    selectProduct(machine) {
        throw new Error('Method not implemented');
    }

    dispense(machine) {
        throw new Error('Method not implemented');
    }
}

// 具体状态类:无硬币状态
class NoCoinState extends VendingMachineState {
    insertCoin(machine) {
        console.log("Coin inserted");
        machine.setState(machine.getHasCoinState());
    }

    selectProduct(machine) {
        console.log("Please insert a coin first");
    }

    dispense(machine) {
        console.log("Please insert a coin first");
    }
}

// 具体状态类:有硬币状态
class HasCoinState extends VendingMachineState {
    insertCoin(machine) {
        console.log("You already inserted a coin");
    }

    selectProduct(machine) {
        console.log("Product selected");
        machine.setState(machine.getSoldState());
    }

    dispense(machine) {
        console.log("No product selected");
    }
}

// 具体状态类:售出状态
class SoldState extends VendingMachineState {
    insertCoin(machine) {
        console.log("Please wait, we're already giving you a product");
    }

    selectProduct(machine) {
        console.log("Please wait, we're already giving you a product");
    }

    dispense(machine) {
        console.log("Product dispensed");
        if (machine.getCount() > 0) {
            machine.setState(machine.getNoCoinState());
        } else {
            console.log("Out of products");
            machine.setState(machine.getSoldOutState());
        }
    }
}

// 具体状态类:售罄状态
class SoldOutState extends VendingMachineState {
    insertCoin(machine) {
        console.log("Machine is sold out, cannot accept coins");
    }

    selectProduct(machine) {
        console.log("Machine is sold out, cannot select product");
    }

    dispense(machine) {
        console.log("No product to dispense");
    }
}

// 自动售货机类
class VendingMachine {
    #noCoinState;
    #hasCoinState;
    #soldState;
    #soldOutState;
    #currentState;
    #count = 0;

    constructor(productCount) {
        this.#noCoinState = new NoCoinState();
        this.#hasCoinState = new HasCoinState();
        this.#soldState = new SoldState();
        this.#soldOutState = new SoldOutState();
        this.#count = productCount;
        this.#currentState = productCount > 0 ? this.#noCoinState : this.#soldOutState;
    }

    insertCoin() {
        this.#currentState.insertCoin(this);
    }

    selectProduct() {
        this.#currentState.selectProduct(this);
    }

    dispense() {
        this.#currentState.dispense(this);
        if (this.#currentState !== this.#soldOutState) {
            this.#count--;
        }
    }

    setState(state) {
        this.#currentState = state;
    }

    getCount() {
        return this.#count;
    }

    getNoCoinState() { return this.#noCoinState; }
    getHasCoinState() { return this.#hasCoinState; }
    getSoldState() { return this.#soldState; }
    getSoldOutState() { return this.#soldOutState; }
}

// 使用示例
function demonstrateState() {
    const machine = new VendingMachine(2);

    console.log("Attempting to buy without inserting coin:");
    machine.selectProduct();

    console.log("\nInserting coin and selecting product:");
    machine.insertCoin();
    machine.selectProduct();
    machine.dispense();

    console.log("\nAttempting to buy second product:");
    machine.insertCoin();
    machine.selectProduct();
    machine.dispense();

    console.log("\nAttempting to buy when machine is empty:");
    machine.insertCoin();
    machine.selectProduct();
    machine.dispense();
}

demonstrateState();

实现思路

  1. VendingMachineState 接口:定义了所有具体状态类必须实现的方法,包括 insertCoin, selectProduct, 和 dispense

  2. 具体状态类(NoCoinState, HasCoinState, SoldState, SoldOutState

    • 每个类代表自动售货机的一种特定状态。
    • 每个类都实现了 VendingMachineState 接口中定义的方法。
    • 这些方法定义了在特定状态下,对各种操作的响应。
  3. VendingMachine

    • 使用私有字段来存储所有可能的状态和当前状态。
    • 提供了插入硬币、选择产品和发放产品的方法。
    • 这些方法简单地委托给当前状态对象。
    • 提供了 setState 方法来改变当前状态。

优点

  • 单一职责原则:每个状态类只负责处理该状态下的行为。
  • 开闭原则:可以轻松添加新的状态而不修改现有代码。
  • 消除了复杂的条件语句:避免了使用大量的 if-else 语句。
  • 状态转换的显式化:状态转换逻辑清晰可见。