DPDesign Pattern Quest
#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 問のクイズで、 このパターンを身体に染み込ませよう。

挑戦する →