在開發大型應用程式時,我們常常會遇到類別之間相互依賴的情況。如果你曾在 CodeIgniter 4 中遇到過 “模型之間載入就會壞掉” 的問題,那麼你很可能陷入了一個相當棘手的循環依賴陷阱。本文將探討循環依賴的問題來源,並提供一些有效的解決方案。

什麼是循環依賴?

循環依賴(Circular Dependency)指的是兩個或多個類別之間互相依賴彼此,這樣的結構會導致初始化的死循環,最終導致應用程式無法正常運行。例如,當模型 A 需要用到模型 B,而模型 B 又需要用到模型 A,這樣的結構會讓程式在不斷地嘗試初始化這些模型時陷入無限迴圈。

舉例來說,假設我們有兩個模型:OrderModelOrderPaymentRecordsModel

  • OrderModel:負責訂單相關的資料處理。
  • OrderPaymentRecordsModel:負責付款記錄的資料處理。

OrderModel 中,我們需要使用 OrderPaymentRecordsModel 來處理付款記錄,而在 OrderPaymentRecordsModel 中,又需要引用 OrderModel 來取得訂單的資訊。這種互相引用的情況就會導致循環依賴的發生。

問題示例

以下是 OrderModelOrderPaymentRecordsModel 中的代碼示例,這裡展示了它們之間的相互依賴問題:

class OrderModel extends BaseModel
{
    protected $order_payment_records;

    public function __construct()
    {
        parent::__construct();
        $this->order_payment_records = new OrderPaymentRecordsModel();
    }
}
class OrderPaymentRecordsModel extends BaseModel
{
    protected $order;

    public function __construct()
    {
        parent::__construct();
        $this->order = new OrderModel();
    }
}

在這樣的結構中,當你載入 OrderModel 時,它會創建 OrderPaymentRecordsModel 的實例,而這個實例在初始化時又會去創建 OrderModel 的實例,這樣會導致應用程式無法正常運行,甚至直接崩潰。

解決方案

要解決這種循環依賴問題,我們可以採取幾種方法:

1. 延遲載入(Lazy Loading)

延遲載入是一種有效的解決方案,可以避免在建構子中直接創建所有依賴。簡單來說,只有在確實需要使用這些依賴時,我們才進行初始化。

修改後的 OrderPaymentRecordsModel

class OrderPaymentRecordsModel extends BaseModel
{
    protected $order;

    protected function getOrderModel()
    {
        if ($this->order === null) {
            $this->order = new OrderModel();
        }
        return $this->order;
    }

    public function set_status($id, $status, $response_json = '{}', $remark = '----')
    {
        $orderModel = $this->getOrderModel();
        $order = $orderModel->where('order_id', $order_payment_records['order_id'])->first();
        // 其餘代碼...
    }
}

這樣做可以確保 OrderModel 只在需要時才會被初始化,避免在建構子中引發循環依賴。

2. 依賴注入(Dependency Injection)

另一種方式是透過依賴注入的方式來解決問題。我們可以在控制器或服務類中將所需的依賴傳入,而不是在模型內部自己創建它們。

例如:

class OrderPaymentRecordsModel extends BaseModel
{
    protected $order;

    public function __construct(OrderModel $order)
    {
        parent::__construct();
        $this->order = $order;
    }
}

在控制器中:

$orderModel = new OrderModel();
$orderPaymentRecordsModel = new OrderPaymentRecordsModel($orderModel);

這樣可以確保所有的依賴都由上層控制器注入,而不是在模型內部自己建立,避免了循環依賴。

3. 重構業務邏輯到服務層

如果兩個模型的交互邏輯非常複雜,將共用的邏輯提取到服務層是一種較好的選擇。這樣可以將模型的責任分開,減少模型之間的直接依賴。

例如,創建一個 OrderService

class OrderService
{
    protected $orderModel;

    public function __construct()
    {
        $this->orderModel = new OrderModel();
    }

    public function getOrderById($orderId)
    {
        return $this->orderModel->find($orderId);
    }
}

然後在 OrderPaymentRecordsModel 中使用這個服務類:

class OrderPaymentRecordsModel extends BaseModel
{
    protected $orderService;

    public function __construct()
    {
        parent::__construct();
        $this->orderService = new OrderService();
    }

    public function set_status($id, $status, $response_json = '{}', $remark = '----')
    {
        $order = $this->orderService->getOrderById($orderPaymentRecords['order_id']);
        // 其餘代碼...
    }
}

透過將邏輯移到服務層,我們可以讓模型更加專注於資料處理,減少它們之間的耦合,並提高程式碼的可維護性。

最後修改日期: 2024 年 10 月 28 日

作者

留言

撰寫回覆或留言

發佈留言必須填寫的電子郵件地址不會公開。