#07 / 23
S構造DIFF
ブリッジ
Bridge
リモコンとテレビ。操作する側と中身を別物として独立に増やせる。組合せ爆発を防ぐ設計の知恵。
Intent · 目的
『機能の階層』と『実装の階層』を別の継承ツリーに切り分け、それぞれ独立に拡張できるようにする。1本の継承ツリーに全部押し込むと組合せが M×N に膨れ上がるところを、M+N で済ませる。
Motivation · 動機
テレビとリモコンを思い浮かべよう。リモコンには『汎用』『学習リモコン』『高機能リモコン』があり、テレビ側にも『液晶』『有機EL』『プロジェクタ』がある。これを「液晶用汎用リモコン」「液晶用学習リモコン」「OLED用…」と全組合せで作ったら売り場が崩壊する。リモコンは『何をするか』を、テレビは『どう実現するか』を担当 ── 両者を独立させて自由に組み合わせる、それがブリッジ。図形 × 描画API、UI部品 × プラットフォームなど、2軸で増える設計に効く。
適用場面
- 1クラスが2方向で継承を伸ばしており、組合せ爆発しそうな場合(機能 × プラットフォーム)。
- 2抽象側と実装側を実行時に切り替えたい場合。
- 3プラットフォーム別の実装を共通APIで扱いたい場合(SVG / Canvas、Win / Mac / Linux)。
概念図
サンプルコード
interface Renderer {
renderCircle(r: number): void;
}
class SvgRenderer implements Renderer {
renderCircle(r: number){ console.log(`<circle r='${r}'/>`); }
}
class CanvasRenderer implements Renderer {
renderCircle(r: number){ console.log(`ctx.arc(0,0,${r})`); }
}
abstract class Shape {
constructor(protected renderer: Renderer) {}
abstract draw(): void;
}
class Circle extends Shape {
constructor(renderer: Renderer, private r: number){ super(renderer); }
draw(){ this.renderer.renderCircle(this.r); }
}
new Circle(new SvgRenderer(), 5).draw();
new Circle(new CanvasRenderer(), 5).draw();Pros · メリット
- +M×N の組合せ爆発を M+N に抑えられる。種類の追加コストが線形になる。
- +実装を実行時に差し替えられる(SVG → Canvas へ即切替、など)。
- +プラットフォーム依存箇所を実装側に閉じ込められ、抽象側はプラットフォームを意識しない。
Cons · デメリット
- −クラスとI/Fが増えて、最初から構造が重い。最低限のドメインに当てるとオーバーエンジニアリング。
- −『2軸目が必要になる』未来を見越した予言が当たらないと、ただの抽象コストになる。
- −両軸を同時に把握する必要があり、新参者の認知負荷は若干高い。
関連パターン
⚔ ready to test
理解度を確かめる時間だ
3 問のクイズで、 このパターンを身体に染み込ませよう。