Null Object Pattern,PHP 空物件模式

minimalist photography of open door

簡介

Null Object Pattern 主要用於簡化對 Null 或不存在物件的處理。這篇文章將介紹 Null Object Pattern,包括其定義、適用情境,以及如何在 PHP 中實現它。

我們將使用一個與咖啡相關的範例來說明這個模式的實際應用。文章的目的是讓讀者能夠理解 Null Object Pattern 的優點和使用場景,並能在自己的 PHP 項目中實施。

什麼是 Null Object Pattern

Null Object Pattern 是一種設計模式,用於提供一個替代物件來避免 null 引用。這個模式通常用於減少程式碼中的 null 檢查和條件語句。

想像你去一家咖啡店,你可以選擇加糖或不加糖。在不使用 Null Object Pattern 的情況下,咖啡機需要檢查是否有糖,這會使程式碼變得複雜。使用 Null Object Pattern,咖啡機會有一個「無糖」的選項,這樣就不需要進行任何 null 檢查。

null object pattern class diagram
Null Object Pattern 類別圖

什麼狀況適合使用 Null Object Pattern

Null Object Pattern 主要適用於需要頻繁進行 null 檢查的情境。這樣的設計模式可以讓你的程式碼更乾淨,更易於維護。它也有助於提高程式的可讀性和可測試性。

例如,在一個大型電商網站中,可能有多種支付方式。使用 Null Object Pattern 可以避免在每次檢查支付方式是否存在時都寫冗長的條件語句。

範例教學

原始範例,未使用 Null Object

// 定義 CoffeeMachine 類別
class CoffeeMachine {
    // 定義一個私有變數 $sugar 來儲存是否需要添加糖
    private $sugar;

    // 建構函數,接受一個布林值來決定是否需要添加糖
    public function __construct($sugar) {
        $this->sugar = $sugar;
    }

    // 製作咖啡的方法
    public function makeCoffee() {
        // 檢查是否需要添加糖
        if ($this->sugar) {
            echo "Adding sugar\n";
        }
        echo "Making coffee\n";
    }
}

// 使用範例
$coffeeMachineWithSugar = new CoffeeMachine(true);
$coffeeMachineWithSugar->makeCoffee();  // Output: "Adding sugar\nMaking coffee\n"

$coffeeMachineWithoutSugar = new CoffeeMachine(false);
$coffeeMachineWithoutSugar->makeCoffee();  // Output: "Making coffee\n"

在這個原始範例中,CoffeeMachine 類別有一個私有變數 $sugar,用於儲存是否需要添加糖。在 makeCoffee 方法中,我們需要用 if 語句來檢查是否需要添加糖。

這種做法的主要缺點是,每次調用 makeCoffee 方法時,都需要進行條件檢查。這會使程式碼變得更複雜,也更難維護。而且,如果未來需要添加更多的條件或行為(例如添加牛奶、調整糖的量等),這種做法會變得更不可取。

改善範例,使用 Null Object

// 定義一個 Sugar 介面,包含一個 add 方法
interface Sugar {
    public function add();
}

// 實現 Sugar 介面的 RegularSugar 類別
class RegularSugar implements Sugar {
    // 添加糖的方法
    public function add() {
        echo "Adding sugar\n";
    }
}

// 實現 Sugar 介面的 NullSugar 類別
class NullSugar implements Sugar {
    // 這個方法什麼都不做,因為這是 Null Object
    public function add() {
        // Do nothing
    }
}

// 定義 CoffeeMachine 類別
class CoffeeMachine {
    // 定義一個私有變數 $sugar 來儲存糖的類型
    private $sugar;

    // 建構函數,接受一個 Sugar 類型的參數
    public function __construct(Sugar $sugar) {
        $this->sugar = $sugar;
    }

    // 製作咖啡的方法
    public function makeCoffee() {
        // 調用糖的 add 方法
        $this->sugar->add();
        echo "Making coffee\n";
    }
}

// 使用範例
$coffeeMachineWithSugar = new CoffeeMachine(new RegularSugar());
$coffeeMachineWithSugar->makeCoffee();  // Output: "Adding sugar\nMaking coffee\n"

$coffeeMachineWithoutSugar = new CoffeeMachine(new NullSugar());
$coffeeMachineWithoutSugar->makeCoffee();  // Output: "Making coffee\n"

這個範例中,我們定義了一個 Sugar 介面和兩個實現這個介面的類別:RegularSugarNullSugarRegularSugar 類別的 add 方法會輸出 “Adding sugar”,而 NullSugar 類別的 add 方法什麼都不做。

然後,我們在 CoffeeMachine 類別中使用這些 Sugar 類別。這樣,當我們製作咖啡時,就不需要檢查糖是否存在,因為 NullSugar 類別會處理這個情況。

這樣,我們成功地使用了 Null Object Pattern 來簡化程式碼和提高可讀性。

更多延伸範例

這個範例添加牛奶的選項。

// 定義一個 Ingredient 介面,包含一個 add 方法
interface Ingredient {
    public function add();
}

// 實現 Ingredient 介面的 RegularSugar 類別
class RegularSugar implements Ingredient {
    public function add() {
        echo "Adding sugar\n";
    }
}

// 實現 Ingredient 介面的 RegularMilk 類別
class RegularMilk implements Ingredient {
    public function add() {
        echo "Adding milk\n";
    }
}

// 實現 Ingredient 介面的 NullIngredient 類別
class NullIngredient implements Ingredient {
    public function add() {
        // Do nothing
    }
}

// 定義 CoffeeMachine 類別
class CoffeeMachine {
    private $sugar;
    private $milk;

    public function __construct(Ingredient $sugar, Ingredient $milk) {
        $this->sugar = $sugar;
        $this->milk = $milk;
    }

    public function makeCoffee() {
        $this->sugar->add();
        $this->milk->add();
        echo "Making coffee\n";
    }
}

// 使用範例
$coffeeMachine1 = new CoffeeMachine(new RegularSugar(), new NullIngredient());
$coffeeMachine1->makeCoffee();  // Output: "Adding sugar\nMaking coffee\n"

$coffeeMachine2 = new CoffeeMachine(new NullIngredient(), new RegularMilk());
$coffeeMachine2->makeCoffee();  // Output: "Adding milk\nMaking coffee\n"

$coffeeMachine3 = new CoffeeMachine(new NullIngredient(), new NullIngredient());
$coffeeMachine3->makeCoffee();  // Output: "Making coffee\n"

在這個延伸範例中,定義了一個新的 Ingredient 介面,並讓 RegularSugarRegularMilkNullIngredient 類別實現這個介面。

然後,在 CoffeeMachine 類別中添加了一個新的私有變數 $milk,用於儲存牛奶的類型。

這樣,不僅可以輕鬆地添加糖和牛奶,還可以在未來輕鬆地添加更多的成分,而不需要一直檢查 Null。

其他類似的 Design Pattern

Null Object Pattern 和其他設計模式,如 Factory Pattern 或 Singleton Pattern,有一些相似之處,但主要的區別在於它們解決的問題。Null Object 主要用於消除 null 檢查,而 Factory 和 Singleton 則解決不同的問題。

Design Pattern主要用途
Null Object消除 null 檢查
Factory創建對象
Singleton限制對象的實例化

參考來源

  1. Wikipedia: Null Object Pattern
  2. GeeksforGeeks: Null Object Pattern

發佈留言

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