簡單介紹設計模式 - 外觀模式

外觀模式為一種結構型設計模式,主要為複雜的函式庫(Library)或是複雜的類別提供一個統一且簡單的高級介面。

要處理的問題


假設你的程式需要使用某個複雜的函式庫。一般來說,你需要負責函式庫中所需物件的初始化工作,除了需要管理這些物件的依賴關係,還需要按照順序執行這些物件的方法來幫助你完成目的。

但這麼做的結果,有可能讓你的程式碼業務邏輯與函式庫緊密的耦合在一起,導致日後維護上的困難。

解決方案


如果你的程式碼需要用到幾十種功能複雜的函式庫,但卻只需要這些函式庫中的少部分功能,那麼使用外觀模式會非常方便。

新建一個外觀類別,這個類別會為許多複雜的子系統提供一個簡單的介面,與直接調用子系統相比,外觀類別提供的功能雖然比較有限,但卻是客戶端真正需要的功能。

例子


上傳影片到社交媒體網站的應用程式,上傳影片的部分需要使用專業的影片編碼函式庫,編碼函式庫雖然提供很多功能,但我們只需要函示庫中的 encode(filename, format) 方法 (以檔案名稱與格式為參數進行編碼)。

在這樣的情況下,你可以創建一個外觀類別來操作影片編碼函示庫,這樣客戶端只需要調用外觀類別,可以與編碼函式庫隔離開來。

架構


2022_04_17_22_20_32_625c22305c5b6.png
  • Facade (外觀類別):為多個複雜子系統提供一個簡單的高級介面,會負責子系統的初始化與調用。
  • Additional Facade (附加外觀):因為外觀類別需要依賴多個子系統,為了避免讓外觀類別太複雜,較為不相關的功能可以再切出來做成一個外觀。
  • Complex Subsystem (複雜子系統):由數個不同的子系統組成的專門系統,如果要使用這些子系統完成工作,你可能需要深入了解每個子系統,例如按照正確的順序初始化物件或為其提供正確的參數。

使用 PHP 實作外觀模式


假設我們有一個下載 Youtube 影片的應用程式,這個下載影片的過程需要操作 Youtube API 與 FFmpeg 函式庫。

我們可以使用外觀模式操作 Youtube API 與 FFmpeg 實作一個下載影片的方法,客戶端只需要操作外觀類別即可。

假設我們需要調用 Youtube API 與 FFmpeg 這樣較為複雜的子系統。

// The YouTube API subsystem.
class YouTube
{
    public function fetchVideo(): string
    {
        // do some stuff
    }

    public function saveAs(string $path): void
    {
        // do some stuff
    }

    // ...more methods
}

// The FFmpeg subsystem (a complex video/audio conversion library).
class FFMpeg
{
    public static function create(): FFMpeg
    {
        // do some stuff
    }

    public function open(string $video): void
    {
        // do some stuff
    }

    // ...more methods
}

class FFMpegVideo
{
    public function filters(): self
    {
        // do some stuff
    }

    public function resize(): self
    {
        // do some stuff
    }

    public function synchronize(): self
    {
        // do some stuff
    }

    public function frame(): self
    {
        // do some stuff
    }

    public function save(string $path): self
    {
        // do some stuff
    }

    // ...more methods
}

這時候我們可以實作一個外觀類別 YoutubeDownloader,操作 Youtube API 與 FFmpeg 來下載影片。

class YouTubeDownloader
{
    protected $youtube;
    protected $ffmpeg;

    public function __construct(string $youtubeApiKey)
    {
        $this->youtube = new YouTube($youtubeApiKey);
        $this->ffmpeg = new FFMpeg();
    }

    public function downloadVideo(string $url): void
    {
        echo "Fetching video metadata from youtube...\\n";
        // $title = $this->youtube->fetchVideo($url)->getTitle();
        echo "Saving video file to a temporary file...\\n";
        // $this->youtube->saveAs($url, "video.mpg");
        echo "Processing source video...\\n";
        // $video = $this->ffmpeg->open('video.mpg');
        echo "Normalizing and resizing the video to smaller dimensions...\\n";
        // $video
        //     ->filters()
        //     ->resize(new FFMpeg\\Coordinate\\Dimension(320, 240))
        //     ->synchronize();
        echo "Capturing preview image...\\n";
        // $video
        //     ->frame(FFMpeg\\Coordinate\\TimeCode::fromSeconds(10))
        //     ->save($title . 'frame.jpg');
        echo "Saving video in target formats...\\n";
        // $video
        //     ->save(new FFMpeg\\Format\\Video\\X264(), $title . '.mp4')
        //     ->save(new FFMpeg\\Format\\Video\\WMV(), $title . '.wmv')
        //     ->save(new FFMpeg\\Format\\Video\\WebM(), $title . '.webm');
        echo "Done!\\n";
    }
}

客戶端只需要操作外觀類別,可以與複雜的 Youtube API 還有 FFmpeg 函式庫隔離開來。

function clientCode(YouTubeDownloader $facade)
{
    // ...

    $facade->downloadVideo("https://www.youtube.com/watch?v=example");

    // ...
}

$facade = new YouTubeDownloader("APIKEY-XXXXXXXXX");
clientCode($facade);

外觀模式的優點


  • 讓客戶端能與複雜子系統隔離開來
  • 為複雜子系統的常用功能,提供一個快捷使用的方式

參考資料


sharkHead
written by
sharkHead

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