問題:到處都是 Null 檢查#

在許多應用程式中,程式碼會不斷地出現 null 檢查。例如,從資料庫中取得員工時,必須先確認回傳值不為 null 才能操作:

Employee e = DB.GetEmployee("Bob");
if (e != null && e.IsTimeToPay(today))
  e.Pay();

這種程式碼又醜又容易出錯。如果任何地方忘了檢查 null,就會在執行時引發 NullReferenceException。而且這種檢查散佈在程式碼的每個角落,增加了閱讀和維護的困難。

Null Object 模式#

結構#

Null Object 模式的解法是提供一個代表「什麼都不做」的物件來取代 null。以員工系統為例:

Figure 25.1: NULL OBJECT

  • Employee 變成帶有 IsTimeToPay()Pay() 等方法的抽象類別(或介面)
  • EmployeeImplementation 是實際的員工實作
  • NullEmployee 是空物件實作——所有方法都不做任何事IsTimeToPay() 回傳 falsePay() 什麼都不做

實作方式#

NullEmployee 被設計為 Employee 內部的私有巢狀類別(private nested class),外部無法直接存取。Employee 類別提供一個靜態唯讀欄位 NULL

public abstract class Employee
{
  public static readonly Employee NULL = new NullEmployee();

  public abstract bool IsTimeToPay(DateTime date);
  public abstract void Pay();

  private class NullEmployee : Employee
  {
    public override bool IsTimeToPay(DateTime date)
    {
      return false;
    }

    public override void Pay()
    {
    }
  }
}

使用方式#

DB.GetEmployee() 永遠回傳一個 Employee 實例——找不到時回傳 Employee.NULL,不再回傳 null:

Employee e = DB.GetEmployee("Bob");
e.Pay(); // 安全,不需要 null 檢查

如果真的需要區分是否為空物件,可以透過比較靜態欄位來判斷:

if (e == Employee.NULL)
{
  // 處理找不到員工的情況
}

因為 Employee.NULL 是唯一的 NullEmployee 實例(static readonly),這個比較是可靠的。

重點: Null Object 模式的關鍵好處是消除散佈在整個程式碼中的 null 檢查。呼叫端不需要關心取得的物件是「真正的」員工還是空物件——它只管呼叫方法,空物件會以「什麼都不做」的方式安全回應。這讓程式碼更乾淨、更簡潔,也更不容易出錯。

何時使用 Null Object#

  • 當系統中有大量的 null 檢查散佈各處,而 null 代表的語意是「什麼都不做」時
  • 當可以定義一個明確的「空行為」(do-nothing behavior)時
  • 將 Null Object 設計為私有巢狀類別是常見的做法,這確保了只有一個 null 實例存在,且外部無法任意建立

技巧: Null Object 模式是一個簡單但非常實用的模式。它經常與其他模式搭配使用——例如在後續的薪資系統案例中,Employee 類別的 Affiliation 預設值就使用了 NoAffiliation(一種 Null Object),避免在計算扣款時需要檢查員工是否有加入工會。