State Pattern,PHP 狀態模式

silver and black espresso maker on cafe

簡介

State Pattern 是一種行為型設計模式,用於封裝一個對象的狀態相關行為。這種模式允許對象在運行時改變其行為,而不需要修改對象的類別。這樣可以使狀態轉換更加清晰,並且易於維護和擴展。

本文將詳細介紹 State Pattern 的定義、適用情境,並通過一個與咖啡相關的 PHP 範例來說明其實際應用。

什麼是 State Pattern

State Pattern 允許一個對象改變其行為,這一變化是基於其內部狀態。這通常是通過將狀態相關的行為封裝在一個或多個狀態類別中來實現的。

想像一個咖啡機,它有多種狀態,如「待機」、「煮咖啡」和「需要清潔」。使用 State Pattern,我們可以為每個狀態創建一個獨立的類別,並在這些類別中定義相應的行為。

這是一個用來說明 State Pattern 的類別圖。

UML Class Diagram

什麼狀況適合使用 State Pattern

  1. 當一個對象有多個狀態,並且其行為會隨著狀態的改變而改變。
  2. 當一個對象的狀態轉換複雜,並且需要多個條件語句來控制。
  3. 當你需要在運行時動態改變對象的行為。

範例教學

在這個範例中,我們將模擬一台咖啡機的運作。這台咖啡機有三種狀態:「待機」、「煮咖啡」和「需要清潔」。

原始範例 (未使用 State)

在未使用 State Pattern 的情況下,我們可能會使用多個條件語句來控制咖啡機的狀態。

<?php
// 定義 CoffeeMachine 類別
class CoffeeMachine {
    // 定義一個私有變數 $state 來儲存咖啡機的當前狀態,初始狀態為 'idle'(待機)
    private $state = 'idle';

    // 定義一個公共方法 makeCoffee() 來製作咖啡
    public function makeCoffee() {
        // 檢查咖啡機的當前狀態
        if ($this->state === 'idle') {
            // 如果狀態是 'idle'(待機),則製作咖啡
            echo "Making coffee...\n";
            // 製作咖啡後,將狀態設置為 'needCleaning'(需要清潔)
            $this->state = 'needCleaning';
        } elseif ($this->state === 'needCleaning') {
            // 如果狀態是 'needCleaning'(需要清潔),則提示用戶先清潔咖啡機
            echo "Please clean the machine first.\n";
        }
    }

    // 定義一個公共方法 cleanMachine() 來清潔咖啡機
    public function cleanMachine() {
        // 清潔咖啡機
        echo "Cleaning the machine...\n";
        // 清潔完成後,將狀態設置回 'idle'(待機)
        $this->state = 'idle';
    }
}

這樣的設計會讓狀態轉換邏輯分散在多個方法中,難以維護,在新增或修改狀態時,也會讓程式更加複雜。

改善範例 (使用 State)

使用 State Pattern,我們可以將每個狀態的行為封裝在獨立的類別中。

<?php
// 定義一個 State 介面,包含 makeCoffee 和 cleanMachine 兩個方法
interface State {
    public function makeCoffee();
    public function cleanMachine();
}

// 實現 State 介面的 IdleState 類別
class IdleState implements State {
    // 製作咖啡的方法
    public function makeCoffee() {
        echo "Making coffee...\n";
        // 製作咖啡後,返回一個新的 NeedCleaningState 實例
        return new NeedCleaningState();
    }

    // 清潔咖啡機的方法
    public function cleanMachine() {
        echo "No need to clean.\n";
        // 無需清潔,返回當前實例
        return $this;
    }
}

// 實現 State 介面的 NeedCleaningState 類別
class NeedCleaningState implements State {
    // 製作咖啡的方法
    public function makeCoffee() {
        echo "Please clean the machine first.\n";
        // 需要先清潔,返回當前實例
        return $this;
    }

    // 清潔咖啡機的方法
    public function cleanMachine() {
        echo "Cleaning the machine...\n";
        // 清潔完成後,返回一個新的 IdleState 實例
        return new IdleState();
    }
}

// 定義 CoffeeMachine 類別
class CoffeeMachine {
    // 定義一個私有變數 $state 來儲存當前的狀態
    private $state;

    // 建構函數,初始化狀態為 IdleState
    public function __construct() {
        $this->state = new IdleState();
    }

    // 製作咖啡的方法
    public function makeCoffee() {
        // 調用當前狀態的 makeCoffee 方法,並更新狀態
        $this->state = $this->state->makeCoffee();
    }

    // 清潔咖啡機的方法
    public function cleanMachine() {
        // 調用當前狀態的 cleanMachine 方法,並更新狀態
        $this->state = $this->state->cleanMachine();
    }
}
範例類別圖

使用 State Patter 讓狀態轉換邏輯清晰,易於維護,也易於擴展新的狀態。

更多延伸範例

在這個延伸範例中,我們將添加一個新的狀態:「缺水」。

<?php
// 實現 State 介面的 LowWaterState 類別
class LowWaterState implements State {
    // 製作咖啡的方法
    public function makeCoffee() {
        // 因為水量不足,所以提示用戶先加水
        echo "Please add water first.\n";
        // 返回當前狀態(即水量不足的狀態)
        return $this;
    }

    // 清潔咖啡機的方法
    public function cleanMachine() {
        // 因為水量不足,所以提示用戶先加水
        echo "Please add water first.\n";
        // 返回當前狀態(即水量不足的狀態)
        return $this;
    }
}

// CoffeeMachine 類別(其他程式碼不變)
class CoffeeMachine {
    // ... (其他程式碼不變)

    // 新增一個方法用於加水
    public function addWater() {
        // 提示用戶正在加水
        echo "Adding water...\n";
        // 加水後,將咖啡機的狀態設置為 IdleState(待機狀態)
        $this->state = new IdleState();
    }
}

在這個延伸範例中,添加了一個新的狀態:LowWaterState(水量不足狀態)。這個狀態會在咖啡機水量不足時被觸發。

我們也在 CoffeeMachine 類別中添加了一個新的方法:addWater(),用於加水並將咖啡機的狀態重置為 IdleState(待機狀態)。

這樣,當咖啡機處於水量不足狀態時,用戶會被提示先加水,然後可以通過呼叫 addWater() 方法來解決這個問題。

其他類似的 Design Pattern

  1. Strategy Pattern: 雖然它也允許對象在運行時改變行為,但它更注重於封裝一組可互換的算法。
  2. Command Pattern: 它封裝請求作為對象,但不涉及狀態管理。
  3. Observer Pattern: 它用於一對多的依賴通知,但不專注於單一對象的狀態轉換。

參考來源

  1. YouTube: State Pattern – Design Patterns (ep 17)
  2. Refactoring.Guru: State

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *