使用 Swift 開發 App 時,總會遇到類別不斷增加的時刻。
明明只新增一個按鈕,卻要分成淺色模式、深色模式、iOS、iPadOS 版本;組合越多,類別數量就越容易爆炸。
先說結論。Swift 橋接模式會把「做什麼(抽象化)」與「如何做(實作)」分成不同的類別階層,讓兩個軸以相加而非相乘的方式管理。
本文會依序說明子類別爆炸的原因、橋接模式如何避免,以及實際的 Swift 程式碼。
為什麼會發生子類別爆炸?
當兩個彼此不同的變化軸只能用繼承表達時,就會發生子類別爆炸。
來看一個例子。
假設有一個 Remote 類別,可以用它操作 TV 和收音機。
如果還需要「基本遙控器」和「進階遙控器」兩種類型呢?
組合會變成這樣。
- 基本遙控器 × TV
- 基本遙控器 × 收音機
- 進階遙控器 × TV
- 進階遙控器 × 收音機
這樣就已經有 4 個了。
裝置加入「喇叭」後變成 6 個,遙控器加入「語音遙控器」後則增加到 9 個。
有兩個軸時,只使用繼承會讓類別數量以乘法(m×n)增加,而不是加法(m+n)。
這就是子類別爆炸。軸越多,維護負擔就會呈幾何級數增加。
Swift 橋接模式如何避免這個問題?
核心只有一個。
把兩個變化軸各自拆成獨立的階層,再透過參考(組合)連接兩者。
- 抽象化層:遙控器類型(基本/進階)
- 實作層:裝置類型(TV/收音機)
遙控器不會繼承裝置,而是將裝置介面以屬性形式持有。
先將實作層定義成協定,只放入裝置必須提供的最小操作。
// 實作層:裝置必須提供的最小功能
protocol Device {
var volume: Int { get set }
func enable()
func disable()
}
現在,作為抽象化層的遙控器只需要參考 Device。這不是繼承,而是注入。
// 抽象化層:持有裝置並委派工作
class RemoteControl {
let device: Device // 橋接連接點
init(device: Device) { self.device = device }
func togglePower() { /* device.enable/disable 呼叫 */ }
}
這個 device 屬性就是連接兩個階層的橋(Bridge)。
實際擴充後會有什麼不同?
這是我親自擴充程式碼後感受到的差異。
新增進階遙控器時,只要繼承 RemoteControl,在遙控器階層新增一個類別即可。
// 擴充抽象化:裝置程式碼完全不用修改
class AdvancedRemote: RemoteControl {
func mute() {
var d = device
d.volume = 0 // 委派給實作層
}
}
反過來,新增裝置(例如喇叭)時,只要採用 Device 協定即可。遙控器程式碼仍可直接重複使用。
兩個軸完全獨立,因此擴充其中一邊時,不必重新建立另一邊的組合。
用表格比較,差異就很明顯。
| 分類 | 只使用繼承 | 橋接模式 |
|---|---|---|
| 類別增加方式 | 乘法(m×n) | 加法(m+n) |
| 3 種遙控器 × 4 種裝置 | 12 個 | 7 個 |
| 新增裝置時 | 增加與遙控器種類相同的數量 | 只增加 1 個 |
| 執行階段替換 | 困難 | 可透過注入自由替換 |
以 3 種遙控器、4 種裝置為例,類別會從 12 個降為 7 個。軸越多,差距越明顯。
什麼時候該用,什麼時候該避免?
橋接模式不是所有情況的答案。到處濫用,反而容易讓程式碼變複雜。
適合使用的情況:
- 存在兩個以上彼此獨立變化的軸
- 想在執行階段替換實作時(例如實際 API ↔ Mock)
- 想清楚分離不同平台與主題的實作時
可以不使用的情況:
- 只有一個軸,或組合未來也會固定在 2~3 個時
- 結構很簡單,卻先拆成階層而造成過度設計時
Swift 原本就很自然地使用協定與相依性注入,因此有時只是叫不出橋接模式的名字,實際上早已在使用它。
常見問題(Q&A)
Q. 這和 Adapter Pattern 有什麼不同?
Adapter 的目的是事後接上已完成但彼此不相容的程式碼;Bridge 的目的是在設計階段就先分離兩個軸。
Q. 一定要使用 protocol 嗎?
是。在 Swift 中,將實作層定義成 protocol 最自然。使用協定取代抽象類別後,值型別(struct)也能作為實作。
Q. SwiftUI 也能使用嗎?
可以。讓 View 注入資料來源協定,只負責渲染,這本身就是橋接結構。
如果你一直因為子類別不斷增加而感到困擾,今天只要記住一件事。
當你覺得「這個類別有兩個變化原因」時,就是拆分兩個軸的時機。
先親手改寫一個小例子,很快就會熟悉。希望你的程式碼能變得更輕盈!

