Swiftでアプリを作っていると、クラスが際限なく増える瞬間がやってきます。
ボタンを1つ追加しただけなのに、ライトモード用、ダークモード用、iOS用、iPadOS用……組み合わせるほどクラス数が爆発します。
結論から言います。SwiftのBridge Patternは、「何をするか(抽象化)」と「どう実現するか(実装)」を別々のクラス階層に分離し、2つの軸を掛け算ではなく足し算で管理できる構造パターンです。
この記事では、サブクラス爆発が起きる理由、Bridge Patternでの防ぎ方、実際のSwiftコードまで順に見ていきます。
なぜサブクラス爆発は起きるのでしょうか?
サブクラス爆発は、異なる2つの変更軸を継承だけで表現すると起こります。
例を見てみましょう。
Remoteというクラスがあり、このリモコンでTVとラジオを操作するとします。
ここに「標準リモコン」と「高機能リモコン」の2種類も必要になったらどうでしょうか?
組み合わせは次のようになります。
- 標準リモコン × TV
- 標準リモコン × ラジオ
- 高機能リモコン × TV
- 高機能リモコン × ラジオ
これだけですでに4つです。
機器に「スピーカー」を追加すると6つ、リモコンに「音声リモコン」を追加すると9つに増えます。
軸が2つあると、継承だけではクラス数は足し算(m+n)ではなく掛け算(m×n)で増えます。
これがサブクラス爆発です。軸が増えるたびに管理の負担が幾何級数的に大きくなります。
SwiftのBridge Patternはどう防ぐのでしょうか?
核心は1つだけです。
変化する2つの軸をそれぞれ独立した階層に分け、参照(コンポジション)でつなぎます。
- 抽象化層:リモコンの種類(標準/高機能)
- 実装層:機器の種類(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プロパティが、2つの層をつなぐ橋(Bridge)です。
実際に拡張すると、どう変わるのでしょうか?
自分でコードを拡張してみて実感した点です。
高機能リモコンを追加するときは、RemoteControlを継承してリモコン階層にクラスを1つ追加するだけです。
// 抽象化の拡張:機器コードには1行も触れない
class AdvancedRemote: RemoteControl {
func mute() {
var d = device
d.volume = 0 // 実装層に委譲
}
}
一方、新しい機器(例:スピーカー)を追加するなら、Deviceプロトコルに準拠するだけです。リモコンのコードはそのまま再利用できます。
2つの軸は完全に独立しているため、一方を増やしても他方の組み合わせを作り直す必要はありません。
表にすると違いがはっきりします。
| 区分 | 継承のみ | Bridge Pattern |
|---|---|---|
| クラスの増え方 | 掛け算(m×n) | 足し算(m+n) |
| リモコン3種 × 機器4種 | 12個 | 7個 |
| 機器追加時 | リモコンの種類分だけ追加 | 1個だけ追加 |
| 実行時の差し替え | 難しい | 注入で自由に可能 |
リモコン3種、機器4種なら12個が7個になります。軸が増えるほど差は広がります。
いつ使い、いつ避けるべきでしょうか?
Bridge Patternが常に正解とは限りません。むやみに使うと、かえってコードが複雑になります。
次のような場合に向いています。
- 互いに独立して変化する軸が2つ以上あるとき
- 実行時に実装を差し替えたいとき(例:実際のAPI ↔ モック)
- プラットフォーム別・テーマ別の実装をきれいに分けたいとき
次のような場合は不要です。
- 軸が1つだけ、または組み合わせが今後も2~3個に固定されるとき
- 構造が単純なのに、先に階層を分けることで過剰設計になるとき
Swiftではプロトコルと依存性注入が自然に使えるため、Bridge Patternという名前を知らないだけで、すでに使っていたということもあります。
よくある質問(Q&A)
Q. Adapter Patternとは何が違いますか?
Adapterは、すでに作られた互換性のないコードを後からつなぐためのものです。Bridgeは設計段階で最初から2つの軸を分けておくことが目的です。
Q. 必ずprotocolを使う必要がありますか?
はい。Swiftでは実装層をprotocolで定義するのが最も自然です。抽象クラスの代わりにプロトコルを使えば、値型(struct)も実装として利用できます。
Q. SwiftUIでも使えますか?
可能です。ビューがデータソースプロトコルを注入され、レンダリングだけを担当すれば、それがBridge構造です。
サブクラスが増え続けて悩んでいるなら、今日覚えるのは1つだけで十分です。
「このクラスが変わる理由は2つある」と感じたときが、2つの軸を分けるタイミングです。
まずは小さな例を手で書き換えてみてください。思ったより早く身につきます。コードが少しでも軽くなることを願っています!

