1. パターンの意図
ブリッジ(Bridge)パターン は、
抽象(Abstraction)と実装(Implementation)を分離して、それぞれ独立に拡張可能にする デザインパターンです。
解決する問題
- クラスの 多重継承の爆発(組み合わせ爆発) を防ぎたい
- UI テーマ × プラットフォーム のように、2軸で独立に拡張したい
- 抽象(何をするか)と実装(どうやるか)を疎結合に保ちたい
ポイント
- 「抽象(Abstraction)」と「実装(Implementation)」を別クラスに分ける
- 両者は 委譲(delegation) でつながる
- それぞれを独立に拡張できる
2. UML 図
- Abstraction:上位の抽象(例:UIコンポーネント)
- Implementor:下位の実装インターフェース(例:描画エンジン)
- RefinedAbstraction:Abstraction の拡張
- ConcreteImplementor:実装の具体化
3. Flutter / Dart 実装例
3.1 Implementor(実装)
abstract class Renderer {
void render(String shape);
}
class VectorRenderer implements Renderer {
@override
void render(String shape) {
print("Drawing $shape as lines");
}
}
class RasterRenderer implements Renderer {
@override
void render(String shape) {
print("Drawing $shape as pixels");
}
}
3.2 Abstraction(抽象)
abstract class Shape {
Renderer renderer;
Shape(this.renderer);
void draw();
}
class Circle extends Shape {
Circle(Renderer renderer) : super(renderer);
@override
void draw() {
renderer.render("Circle");
}
}
3.3 利用例
void main() {
var vectorCircle = Circle(VectorRenderer());
vectorCircle.draw(); // Drawing Circle as lines
var rasterCircle = Circle(RasterRenderer());
rasterCircle.draw(); // Drawing Circle as pixels
}
4. Android / Kotlin 実装例
4.1 Implementor
interface Renderer {
fun render(shape: String)
}
class VectorRenderer : Renderer {
override fun render(shape: String) {
println("Drawing $shape as lines")
}
}
class RasterRenderer : Renderer {
override fun render(shape: String) {
println("Drawing $shape as pixels")
}
}
4.2 Abstraction
abstract class Shape(protected val renderer: Renderer) {
abstract fun draw()
}
class Circle(renderer: Renderer) : Shape(renderer) {
override fun draw() {
renderer.render("Circle")
}
}
4.3 利用例
fun main() {
val vectorCircle = Circle(VectorRenderer())
vectorCircle.draw() // Drawing Circle as lines
val rasterCircle = Circle(RasterRenderer())
rasterCircle.draw() // Drawing Circle as pixels
}
5. メリット / デメリット
メリット
- 抽象と実装を独立して拡張できる
- 組み合わせ爆発を回避できる
- 例:Shape × Renderer = 2×2 = 4 通り → Bridge で分離すれば管理が楽
- 保守性・テスト性が高い
デメリット
- 設計が少し複雑になる
- 小規模な場合はオーバーエンジニアリング
6. 実務ユースケース
Flutter
- 描画系ライブラリ:Canvas / Skia / CustomPaint の切替
- テーマ切替:Material / Cupertino の UI コンポーネント抽象化
- データ永続化:Repository 抽象 × ストレージ実装(SQLite, SharedPreferences, Hive)
Android (Kotlin)
- View 抽象 × Renderer 実装
- Repository 抽象 × DataSource 実装(Local / Remote)
- UI テーマと描画エンジンの分離
7. 実装上の注意点
Flutter / Dart
- **依存性注入(DI)**と組み合わせると効果的
-
Shape
(抽象)をテストする際にRenderer
をモック化可能
Android / Kotlin
- Repository パターンと一緒に使われることが多い
-
Bridge と Adapter の違いに注意
- Adapter:インターフェース不一致を変換
- Bridge:抽象と実装を分離して並行拡張
8. どんなときに使う?
- 「抽象 × 実装」が組み合わせ爆発しそうなとき
- 両者を独立に進化させたいとき
- プラットフォーム依存部分を切り離したいとき
まとめ
- ブリッジパターンは「抽象と実装を分離」して拡張性を高めるパターン
- Flutter では Renderer と Shape の分離、Android では Repository と DataSource の分離に多用
- Adapter が “既存の不一致解消” なのに対して、Bridge は “将来の拡張性確保” が目的
付記
組み合わせ爆発の例(Bridge 導入前)
例えば 図形(Shape) と 描画方式(Renderer) を両方サブクラスで表現すると:
-
Shape:Circle, Square
-
Renderer:Vector, Raster
これを素直に継承で組み合わせると…
👉 2 × 2 = 4 クラス
👉 もし「Triangle」を追加すれば、3 × 2 = 6クラス に増える。
このように Shape × Renderer の掛け算でクラス数が爆発します。
Bridge 導入後(抽象と実装を分離)
Bridge パターンを使って Shape と Renderer を分離すると:
👉 Shape = 2(Circle, Square)
👉 Renderer = 2(Vector, Raster)
-
Triangle を追加しても Triangle クラス1つ増えるだけ
-
Renderer 側(Vector / Raster)はそのまま再利用可能
-
クラス数は 「掛け算」ではなく「足し算」 で増える
👉 3 + 2 = 5 クラスクラス に増える。
図解イメージまとめ
-
Bridge 導入前:
- CircleVector / CircleRaster / SquareVector / SquareRaster … クラス爆発
-
Bridge 導入後:
- Shape と Renderer を分離し、委譲で組み合わせる
- クラス数は 足し算 で済む