iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 17
2

今天的主題是** Bridge Pattern 橋樑模式**,它的目的是當隨著開發的 feature 增加,類別的數量也跟著急遽增加的時候,解決類別數量過多的問題。橋樑模式幫助我們將一個龐大的類別或是幾個高度相關的類別切分開,分成抽象介面 (abstraction,也稱作 interface) 與實作介面(implemention layer,也稱作 platform) 兩個不同的層級,使兩者可以個各自變化而不影響彼此。

什麼是抽象介面與實作介面?抽象介面是某個實體的高階控制層。這個層級不應該有任何的實作,而是將要實踐的項目 delegate 給實作介面。注意我們在這裡提到的並不是程式語言裡的 abstract classes,兩者是不同的概念。

舉個例子

今天我們要製作一組可愛繽紛的幾何形狀玩具,所以我們先從 Shape 的類別開始,子類別裡有 CircleSquare 兩個形狀。現在想要再帶上這些玩具的顏色資訊,加上 RedBlue,於是我們試著把顏色的資訊也變成子類別。然而這個時候會發現,你已經有兩個形狀的子類別了,再加上兩個不同的顏色,你必須有 2*2 = 4 種子類別才能滿足需求(例如:BlueCircleRedSquare


圖片引用自 Refactoring Guru

假如我們再增加一個形狀 Triangle,子類別的數量就會變成 3*2 = 6 種,會變動的維度有兩個(形狀與顏色),因此類別的數量一不小心就變得非常多。為了改善這個情況,我們將顏色抽出成為新的實體,這些小玩具就會變成 形狀+顏色 的組合,這就是橋樑模式。


圖片引用自 Refactoring Guru

在這張示意圖裡 ShapeColor 的關係就是橋樑模式裡的「橋樑」。

程式碼範例

手機 CellPhone 和對講機 WalkieTalkie 對應到先前例子的 CircleSquare,都是透過子類別來實踐音訊傳輸的功能。現在我們增加不同的音訊傳送方式:加密版與普通版,看看這會如何影響我們的程式碼。

以下用 Swift 來示範:

class AudioCommunicationsDevice {
    func sendAudio() {
    }
}

class CellPhone: AudioCommunicationsDevice {
    override func sendAudio() {
        // Implement function in subclasses
    }
}

class WalkieTalkie: AudioCommunicationsDevice {
    override func sendAudio() {
        // Implement function in subclasses
    }
}

class SecureCellPhone: CellPhone {
    override func sendAudio() {
        // Send encrpted audio
    }
}

class SecureWalkieTalkie: WalkieTalkie {
    override func sendAudio() {
        // Send encrypted audio
    }
}

class PlainAudioCellPhone: CellPhone {
    override func sendAudio() {
        // Send unencrypted audio
    }
}

class PlainAudioWalkieTalkie: WalkieTalkie {
    override func sendAudio() {
        // Send unencrypted audio
    }
}

現在我們有兩個維度的變因(通訊裝置與音訊傳輸方式),當我們的音訊傳輸分成普通與加密兩種,就會有 2*2 = 4 個類別。增加一種音訊傳輸方式就必須再創建兩個類別才能滿足需求。

這時候我們多加入一個室內電話 LandlinePhone 的裝置類別,看看類別的數量有什麼變化。

class LandlinePhone: AudioCommunicationsDevice {
    override func sendAudio() {
        // Implement function in subclasses
    }
}

class SecureLandlinePhone: LandlinePhone {
    override func sendAudio() {
        // Send encrypted audio
    }
}

class PlainAudioLandlinePhone: LandlinePhone {
    override func sendAudio() {
        // Send plain audio
    }
}

我們必須另外創造三個新類別才能滿足增加一個新裝置的需求!這樣的類別增長比率顯然是令人困擾的,每次增加一個新裝置,類別總數就會快速增加。

Bridge Pattern Saves the Day

透過橋樑模式,我們知道可以抽出 AudioHandling 作新的實體,讓我們的程式碼變成 通訊裝置+音訊處理 的組成。讓 AudioHandling 去處理加密/普通音訊。

protocol AudioHandling {
    func handle(audio: Audio) -> Audio
}

class AudioEncryptor: AudioHandling {
    func handle(audio: Audio) -> Audio {
        // Encrypt and return Audio
    }
}

class PlainAudioHandler: AudioHandling {
    func handle(audio: Audio) -> Audio {
        // return default audio
    }
}

我們創建了 AudioHandling protocol,讓每個依循這個 protocol 的類別會負責音訊傳輸。

現在來重新整理通訊裝置的部分。

class CellPhone: AudioCommunicationsDevice {

    var audioHandler: AudioHandling

    init(audioHandler: AudioHandling) {
        super.init()

        self.audioHandler = audioHandler
    }

    override func sendAudio() {
        // Use handle(audio:) to prepare audio, then send audio
    }
}

class WalkieTalkie: AudioCommunicationsDevice {

    var audioHandler: AudioHandling

    init(audioHandler: AudioHandling) {
        super.init()

        self.audioHandler = audioHandler
    }

    override func sendAudio() {
        // Use handle(audio:) to prepare audio, then send audio
    }
}

我們給每個通訊裝置一個 audioHandler 的屬性,讓它幫助我們將裝置裡音訊傳輸的工作「bridge」給 AudioHandling 去執行。

橋樑模式的應用範例如下:我們選擇一種通訊裝置來創建,創建時塞入音訊處理的類別。

let audioHandler = AudioEncryptor()
let walkieTalkie = WalkieTalkie(audioHandler: audioHandler)

walkieTalkie.sendAudio()

Bridge 的優缺點

優點

  • Client 端和高階的抽象介面互動,不會將實作介面的細節暴露。
  • 開放封閉原則 (Open/Closed Principle):你可以個別調整抽象介面與實作介面而不影響彼此。
  • 單一職責原則 (Single Responsibility Principle):你可以專注在抽象介面的高階邏輯,以及實作介面的平台細節。
  • 將實作去耦合,讓它不會一直被綁在一個介面上。
  • 在抽象介面的子類別 (Concrete Abstraction Class) 做的更動不會影響到 Client 端。

缺點

  • 當你把橋樑模式應用在高內聚 (highly cohesive) 的類別上時可能會使程式碼變得複雜。

以上是今天的橋樑模式,感謝你的閱讀,讓我們明天繼續設計模式的學習之路!

參考資料:

  1. Refactoring Guru - Bridge Pattern
  2. Swift Solutions - Bridge Design Pattern

作者:Jo


上一篇
[Design Pattern] State 狀態模式
下一篇
[Design Pattern] Visitor 訪問者模式
系列文
什麼?又是/不只是 Design Patterns!?32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言