CleanMyMac

Adapter Pattern,PHP 適配器模式

Feature image for Adapter Pattern,PHP 適配器模式

簡介

什麼是 Adapter Pattern

Adapter Pattern(適配器模式)是一種結構型設計模式,用於讓兩個不相容的介面能夠協同工作。透過建立一個額外的類別(適配器),將一個介面轉換為另一個客戶端期望的介面,就像電器轉接頭一樣,讓不同規格的插頭能夠使用同一個插座。

什麼狀況適合使用 Adapter Pattern

  1. 整合舊系統:系統需要使用現有的類別,但其介面不符合系統需求。
  2. 整合第三方套件:要整合多個不同的函式庫或 API,它們有不同的介面或資料格式。
  3. 重用遺留程式碼:需要重用一些遺留程式碼,但不希望修改原有的介面或程式碼,以保持系統的穩定性。
  4. 統一介面:希望統一多個不同來源的類別,讓它們使用相同的介面進行操作。

範例教學

我們以一個咖啡機為例來說明 Adapter Pattern 的應用。假設我們有一個舊式咖啡機,它使用的介面與我們新系統不相容。在這種情況下,我們可以使用 Adapter Pattern 來解決這個問題,讓舊式咖啡機能夠無縫整合到新系統中。

原始程式,未使用 Adapter

<?php
// 舊式咖啡機
class OldCoffeeMachine {
    public function makeOldStyleCoffee() {
        return "Making coffee the old-fashioned way.";
    }
}

// 新系統
class CoffeeMaker {
    public function brew() {
        return "Brewing coffee with the new machine.";
    }
}

$oldMachine = new OldCoffeeMachine();
echo $oldMachine->makeOldStyleCoffee();  // 輸出: Making coffee the old-fashioned way.

$newMachine = new CoffeeMaker();
echo $newMachine->brew();  // 輸出: Brewing coffee with the new machine.
?>

這樣的作法有明顯的缺點:無法直接在新系統中使用 OldCoffeeMachine,因為介面不匹配。如果要整合,就必須修改舊有的程式碼,或者撰寫額外的程式碼來處理介面轉換,這不僅增加了維護成本,也可能影響原有系統的穩定性。

改善範例,使用 Adapter

<?php
// Adapter
class CoffeeMachineAdapter {
    private $oldMachine;

    public function __construct(OldCoffeeMachine $oldMachine) {
        $this->oldMachine = $oldMachine;
    }

    public function brew() {
        return $this->oldMachine->makeOldStyleCoffee();
    }
}

$oldMachine = new OldCoffeeMachine();
$adapter = new CoffeeMachineAdapter($oldMachine);

echo $adapter->brew();  // 輸出: Making coffee the old-fashioned way.
?>

透過 Adapter Pattern,我們能夠無縫地整合舊有系統和新系統,無需修改原有類別。無論是新系統還是舊系統,都可以使用相同的 brew() 介面來操作,這樣就實現了介面的統一,讓程式碼更加一致且易於維護。

更多延伸範例

接下來,我們來看一個更複雜的範例,展示多種類型的咖啡機和對應的適配器。這個範例可以幫助我們更清楚地觀察 Adapter Pattern 的優勢,以及需要注意的地方。

<?php
// 舊式咖啡機
class OldCoffeeMachine {
    public function makeOldStyleCoffee() {
        return "Making coffee the old-fashioned way.";
    }
}

// Espresso 咖啡機
class EspressoMachine {
    public function makeEspresso() {
        return "Making an espresso.";
    }
}

// Mocha 咖啡機
class MochaMachine {
    public function makeMocha() {
        return "Making a Mocha coffee.";
    }
}

// Adapter for OldCoffeeMachine
class OldMachineAdapter {
    private $machine;

    public function __construct(OldCoffeeMachine $machine) {
        $this->machine = $machine;
    }

    public function brew() {
        return $this->machine->makeOldStyleCoffee();
    }
}

// Adapter for EspressoMachine
class EspressoMachineAdapter {
    private $machine;

    public function __construct(EspressoMachine $machine) {
        $this->machine = $machine;
    }

    public function brew() {
        return $this->machine->makeEspresso();
    }
}

// Adapter for MochaMachine
class MochaMachineAdapter {
    private $machine;

    public function __construct(MochaMachine $machine) {
        $this->machine = $machine;
    }

    public function brew() {
        return $this->machine->makeMocha();
    }
}

// 使用
$oldMachine = new OldCoffeeMachine();
$oldMachineAdapter = new OldMachineAdapter($oldMachine);
echo $oldMachineAdapter->brew();  // 輸出: Making coffee the old-fashioned way.

$espressoMachine = new EspressoMachine();
$espressoAdapter = new EspressoMachineAdapter($espressoMachine);
echo $espressoAdapter->brew();  // 輸出: Making an espresso.

$mochaMachine = new MochaMachine();
$mochaAdapter = new MochaMachineAdapter($mochaMachine);
echo $mochaAdapter->brew();  // 輸出: Making a Mocha coffee.
?>

透過使用適配器模式,我們能夠輕易地為不同類型的咖啡機建立適配器,並使它們能與我們的新系統無縫地整合。每一種咖啡機都有對應的適配器,將各自的方法轉換為統一的 brew() 介面。

這種方式也讓未來的擴展變得非常容易。如果有新類型的咖啡機出現,只需要為其建立一個新的適配器類別即可,完全不需要修改現有的程式碼。這樣,無論咖啡機如何升級或更換,我們的主程式碼都不需要做任何改動,這正是適配器模式的核心優勢:開閉原則(Open-Closed Principle)——對擴展開放,對修改封閉。

Adapter Pattern 的優缺點

優點

  • 讓不相容的介面能夠協同工作,無需修改原有類別
  • 提高了程式碼的重用性和靈活性
  • 符合單一職責原則,適配器只負責介面轉換
  • 符合開閉原則,易於擴展新功能

缺點

  • 增加程式碼複雜度,需要額外的適配器類別
  • 如果介面差異太大,適配器可能會變得複雜
  • 過度使用可能會讓程式碼結構變得難以理解

其他類似的 Design Pattern

在設計模式中,有幾個與 Adapter Pattern 相似的模式,但它們的用途和應用場景有所不同:

  1. Facade Pattern(外觀模式):隱藏系統的複雜性,提供一個統一的簡化介面。Facade 通常用於簡化複雜子系統的介面,而 Adapter 則用於讓不相容的介面能夠協同工作。
  2. Bridge Pattern(橋接模式):分離一個物件的抽象和實現,使兩者可以獨立地變化。Bridge 著重於解耦抽象與實現,而 Adapter 著重於介面轉換。
  3. Decorator Pattern(裝飾器模式):動態地為物件添加功能,而 Adapter 則是轉換介面而不改變功能。

主要差異

  • Adapter:將一個介面轉換成另一個介面,讓不相容的類別能夠協同工作
  • Facade:提供一個簡化的統一介面來隱藏複雜的子系統
  • Bridge:分離抽象和實現,讓它們可以獨立變化

實際應用場景

Adapter Pattern 在實際開發中經常被使用,常見的應用場景包括:

  1. API 整合:整合不同版本的 API 或第三方服務,統一它們的呼叫方式
  2. 資料庫抽象層:將不同資料庫的介面統一,例如 PDO 就是一個很好的例子
  3. 第三方函式庫整合:整合不同來源的函式庫,讓它們使用統一的介面
  4. 舊系統遷移:在系統重構時,讓舊的類別能夠在新系統中繼續使用

總結

Adapter Pattern 是一個非常實用的設計模式,特別適合在需要整合不相容介面的情況下使用。透過建立適配器類別,我們可以讓舊系統和新系統、不同來源的函式庫或 API 能夠無縫協同工作,同時保持程式碼的整潔和可維護性。

記住,適配器模式的核心思想是「轉換介面,而非改變功能」,這讓我們能夠在不修改原有程式碼的情況下,實現系統的整合和擴展。

參考來源

  1. "Design Patterns: Elements of Reusable Object-Oriented Software" by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides
  2. PHP: The Right Way
  3. Refactoring.Guru - Adapter Pattern