簡單介紹設計模式 - 工廠模式

假設我們有一個系統,當中有一個上傳檔案至 Google 雲端平台的功能,只要有需要上傳檔案的地方,就新建一個 GoogleCloud() 的實例,時間久了之後,你可能會發現程式碼中可能有多個地方都有下面的程式碼。

new GoogleCloud();

假設之後想要加入其他雲端廠商,那麼就會有一堆地方需要修改,因為程式碼中許多地方都依賴於 GoogleCloud(),這個時候我們可以考慮使用工廠模式‧

工廠模式建議使用特殊的工廠方法來代替對於物件構造函式的直接調用 (即 new 語句),雖然說是代替,但工廠中還是用 new 語句來實例化我們需要的物件,只是這個過程被包裝起來。

工廠模式是一種創建設計模式,它提供了一個用於創建實體的抽象工廠,讓子類別可以替換工廠產生的實體。

架構


2022_04_09_12_27_23_62510b2b77288.jpg
  • Creator (抽象類別):定義生產產品的抽象類別。
  • ConcreteCreator (類別):具體用來生產產品的類別。
  • Product (介面):定義產品的介面。
  • ConcreteProduct (類別):具體的產品類別,需要實作產品介面。

使用 PHP 實作工廠模式


我們建立一個工廠 (用來建立雲端連結),這個工廠負責生產產品 (雲端連結)。

我們只需要知道給工廠哪些參數 (例如登入用的帳號密碼),工廠就會給我們需要的產品,我們不需要知道工廠如何生產產品。

首先定義好工廠的抽象類別與產品的介面。

<?php

// 用來產生雲端連結 (產品) 的抽象類別 (工廠)
// 工廠都必須繼承此抽象類別
abstract class CloudStorage
{
    // 創建雲端連結 (產品) 的抽象方法
    abstract public function getCloudStorage(): CloudStorageConnector;

    // 上傳檔案的方法
    public function uploadFileToCloud($filePath): void
    {
        // 建立雲端連結 (產品)
        $cloudStorage = $this->getCloudStorage();

        $cloudStorage->login();
        $cloudStorage->upload($filePath);
        $cloudStorage->logout();
    }
}

// 定義產品 (雲端連結) 的介面
// 產品必須有相同的介面,才能替換工廠中用來生產產品的方法
interface CloudStorageConnector
{
    public function login(): void;

    public function logout(): void;

    public function upload(string $filePath): void;
}

定義好工廠與產品需要實作的基底,就可以開始以此實作用來產生 Google 雲端連結的工廠。

// 產生 Google 雲端連結 (產品) 的工廠類別,需要繼承 CloudStorage
class GoogleCloudStorage extends CloudStorage
{
    public function __construct(
        private string $username,
        private string $password
    ) {
    }

    public function getCloudStorage(): CloudStorageConnector
    {
        return new GoogleCloudStorageConnector($this->username, $this->password);
    }
}

// 連接 Google 雲端 (產品) 的類別,需要實作 CloudStorageConnector 介面
class GoogleCloudStorageConnector implements CloudStorageConnector
{
    public function __construct(
        private string $username,
        private string $password
    ) {
    }

    public function login(): void
    {
        echo 'Send HTTP API request to log in user ' . $this->username . ' with ' . $this->password . "\n";
    }

    public function logout(): void
    {
        echo 'Send HTTP API request to log out user ' . $this->username . "\n";
    }

    public function upload(string $filePath): void
    {
        echo 'upload ' . $filePath . ' to Google Cloud Storage' . "\n";
    }
}

當客戶端需要上傳檔案的時候,就可以使用工廠建立對應的實例,並用實例中的方法上傳檔案。

// 客戶端可以使用任何 CloudStorage 的子類別,因為它不依賴於具體類別。
function clientCode(CloudStorage $creator)
{
    // ...
    $creator->uploadFileToCloud("/home/user/hello_world.jpg");
    // ...
}

clientCode(new OtherCloudStorage('user', 'password'));

假設之後需要加入其他雲端廠商,可以依照之前設定的工廠抽象類別與產品介面,實作新的類別。

// 產生其他雲端連結 (產品) 的工廠類別,需要繼承 CloudStorage
class OtherCloudStorage extends CloudStorage
{
    public function __construct(
        private string $username,
        private string $password
    ) {
    }

    public function getCloudStorage(): CloudStorageConnector
    {
        return new OtherCloudStorageConnector($this->username, $this->password);
    }
}

// 連接其他雲端 (產品) 的類別,需要實作 CloudStorageConnector 介面
class OtherCloudStorageConnector implements CloudStorageConnector
{
    public function __construct(
        private string $username,
        private string $password
    ) {
    }

    public function login(): void
    {
        echo 'Send HTTP API request to log in user ' . $this->username . ' with ' . $this->password . "\n";
    }

    public function logout(): void
    {
        echo 'Send HTTP API request to log out user ' . $this->username . "\n";
    }

    public function upload(string $filePath): void
    {
        echo 'upload ' . $filePath . ' to Other Cloud Storage' . "\n";
    }
}

工廠模式的優點


  • 工廠方法可以把創建產品與實際使用產品的邏輯分開來,降低耦合。
  • 可以在不影響原有程式碼的情況下,擴充創建產品的邏輯 (例如加入新的產品)。

參考資料


sharkHead
written by
sharkHead

後端工程師, PHP 基金會每月 5 鎂小額贊助人 稍微擅長 PHP、Python 與 Google Search,偶爾寫寫 TypeScript 對於逗號後面必須加空格有著絕對的堅持