iT邦幫忙

2025 iThome 鐵人賽

DAY 12
0
自我挑戰組

一條龍的軟體開發到維護,從校園工讀到職場工程師系列 第 12

Day12-Code design我遇到比較常見的patten/適配器模式 (Adapter Pattern)

  • 分享至 

  • xImage
  •  

今天來講下Adapter跟Proxy 這2個除了定義不太一樣以外
其實我在實作的時候它們都是在解決差不多的問題
就是將不相容的程式組合再一起 供使用者使用 但區別在實體跟虛擬

先說 適配器模式 (Adapter Pattern)
轉換一個不相容的介面,變成客戶端所期望的另一個介面。

它的主要目的是解決「不相容」的問題,讓原本無法一起工作的類別可以合作。
它扮演的是一個**「翻譯官」或「轉接頭」**的角色。

解決什麼問題?
當你想使用一個已經存在的類別,但它的介面(方法名稱、參數等)不符合你現在的需求。
當你想整合一個第三方的函式庫,但它的 API 設計與你的系統架構格格不入。
當你需要重構一個舊系統,讓新舊模組能夠並存溝通。

好的,我們用一個簡單且常見的 Java 範例來敘述「適配器模式」。

情境說明

假設我們有一個新的音樂播放器系統,它定義了一個標準的播放介面 NewMediaPlayer,所有新的音樂格式都會實現這個介面。

然而,我們還有一個舊的、無法修改的播放器 OldMediaPlayer,它用來播放一種特殊的 .vlc 格式,而且它的方法名稱和我們的新介面完全不同。

我們的目標是:讓新的播放系統也能播放舊的 .vlc 格式,但又不能去修改舊播放器的程式碼。 這就是適配器模式大顯身手的時刻。


程式碼實作

1. 目標介面 (Target Interface)

這是我們新的播放系統期望的標準介面。它只有一個簡單的 play 方法。

// Target Interface: 新的、標準的播放器介面
public interface NewMediaPlayer {
    void play(String fileName);
}

2. 被適配者 (Adaptee)

這是舊的、介面不相容的播放器類別。注意它的方法名稱是 playVlcSong,和我們的標準介面不同。

// Adaptee: 舊的、已存在的播放器,介面不相容
public class OldMediaPlayer {
    public void playVlcSong(String fileName) {
        System.out.println("使用舊版播放器播放 VLC 檔案: " + fileName);
    }
}

3. 適配器 (Adapter)

這是整個模式的核心。我們建立一個 MediaPlayerAdapter,它實現 (implements) 我們新的 NewMediaPlayer 介面,但在內部持有 (has-a) 一個 OldMediaPlayer 的實例。

play 方法被呼叫時,它會在內部將這個呼叫轉換成對 OldMediaPlayerplayVlcSong 方法的呼叫。

// Adapter: 適配器,讓舊播放器能適用於新介面
public class MediaPlayerAdapter implements NewMediaPlayer {

    private OldMediaPlayer oldMediaPlayer;

    // 在建構時,傳入需要被適配的舊物件
    public MediaPlayerAdapter(OldMediaPlayer oldMediaPlayer) {
        this.oldMediaPlayer = oldMediaPlayer;
    }

    @Override
    public void play(String fileName) {
        // 這裡就是「轉換」或「適配」的核心
        // 客戶端呼叫 play(fileName),適配器內部將其轉為呼叫 oldMediaPlayer.playVlcSong(fileName)
        System.out.println(">> 透過適配器...");
        oldMediaPlayer.playVlcSong(fileName);
    }
}

4. 客戶端 (Client)

現在,我們的客戶端(例如主程式)可以無差別地使用 NewMediaPlayer 介面來播放所有音樂,完全不需要知道背後是否有一個舊的播放器在運作。

public class Main {
    public static void main(String[] args) {
        // 建立一個舊的播放器實例
        OldMediaPlayer oldPlayer = new OldMediaPlayer();

        // **關鍵步驟**: 使用適配器將舊播放器包裝起來,讓它看起來像一個新的播放器
        NewMediaPlayer adaptedPlayer = new MediaPlayerAdapter(oldPlayer);

        System.out.println("--- 播放 MP4 (新格式) ---");
        // 假設我們有一個 Mp4Player 類別 (這裡省略實作)
        // NewMediaPlayer mp4Player = new Mp4Player();
        // mp4Player.play("music.mp4");

        System.out.println("\n--- 播放 VLC (舊格式) ---");
        // 對於客戶端來說,它根本不知道這是一個適配器,
        // 它只知道這個物件符合 NewMediaPlayer 介面,可以直接呼叫 play() 方法。
        adaptedPlayer.play("alone.vlc");
    }
}

執行結果:

--- 播放 VLC (舊格式) ---
>> 透過適配器...
使用舊版播放器播放 VLC 檔案: alone.vlc

總結

在這個範例中:

  • Client (Main 類別) 只想使用 NewMediaPlayer 這個標準介面。
  • OldMediaPlayer 介面不符,無法直接使用。
  • MediaPlayerAdapter 就像一個轉接頭,它對外宣稱自己是一個 NewMediaPlayer,但內部卻悄悄地將工作交給了 OldMediaPlayer 去完成。

透過這種方式,我們在不修改任何既有程式碼的情況下,成功地讓兩個不相容的介面協同工作,完美體現了適配器模式的價值。


上一篇
Day11-Code design我遇到比較常見的patten/工廠模式 (Factory Pattern)
下一篇
Day13-Code design我遇到比較常見的patten/代理人模式 (Proxy Pattern)
系列文
一條龍的軟體開發到維護,從校園工讀到職場工程師13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言