今天的主題是** Bridge Pattern 橋樑模式**,它的目的是當隨著開發的 feature 增加,類別的數量也跟著急遽增加的時候,解決類別數量過多的問題。橋樑模式幫助我們將一個龐大的類別或是幾個高度相關的類別切分開,分成抽象介面 (abstraction,也稱作 interface) 與實作介面(implemention layer,也稱作 platform) 兩個不同的層級,使兩者可以個各自變化而不影響彼此。
什麼是抽象介面與實作介面?抽象介面是某個實體的高階控制層。這個層級不應該有任何的實作,而是將要實踐的項目 delegate 給實作介面。注意我們在這裡提到的並不是程式語言裡的 abstract classes,兩者是不同的概念。
今天我們要製作一組可愛繽紛的幾何形狀玩具,所以我們先從 Shape
的類別開始,子類別裡有 Circle
和 Square
兩個形狀。現在想要再帶上這些玩具的顏色資訊,加上 Red
和 Blue
,於是我們試著把顏色的資訊也變成子類別。然而這個時候會發現,你已經有兩個形狀的子類別了,再加上兩個不同的顏色,你必須有 2*2 = 4 種子類別才能滿足需求(例如:BlueCircle
和 RedSquare
)
圖片引用自 Refactoring Guru
假如我們再增加一個形狀 Triangle
,子類別的數量就會變成 3*2 = 6 種,會變動的維度有兩個(形狀與顏色),因此類別的數量一不小心就變得非常多。為了改善這個情況,我們將顏色抽出成為新的實體,這些小玩具就會變成 形狀+顏色
的組合,這就是橋樑模式。
圖片引用自 Refactoring Guru
在這張示意圖裡 Shape
和 Color
的關係就是橋樑模式裡的「橋樑」。
手機 CellPhone
和對講機 WalkieTalkie
對應到先前例子的 Circle
和 Square
,都是透過子類別來實踐音訊傳輸的功能。現在我們增加不同的音訊傳送方式:加密版與普通版,看看這會如何影響我們的程式碼。
以下用 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
}
}
我們必須另外創造三個新類別才能滿足增加一個新裝置的需求!這樣的類別增長比率顯然是令人困擾的,每次增加一個新裝置,類別總數就會快速增加。
透過橋樑模式,我們知道可以抽出 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()
以上是今天的橋樑模式,感謝你的閱讀,讓我們明天繼續設計模式的學習之路!
作者:Jo