1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

本記事は、GoFデザインパターン解説シリーズの1つです。

今回は、構造に関するパターンの中のBridge(ブリッジ)パターンを解説します。

継承の使いすぎによる「クラスの組み合わせ爆発」を防ぐパターンです。

解決したい課題

「機能の種類」と「実装の種類」という2つの独立した軸を、両方とも継承で表現しようとすると、クラス数が組み合わせの数だけ爆発的に増えてしまう問題があります。

例えば、「図形(Shape)」を「描画方法(Renderer)」ごとに変えたいとします。

図形には「円」「四角形」があり、描画方法には「ベクター描画」「ラスター描画」があるとします。

これを愚直に継承だけで表現しようとすると、VectorCircle・RasterCircle・VectorRectangle・RasterRectangleのように、図形の種類 × 描画方法の種類だけクラスが必要になり、新しい図形や描画方法を1つ追加するたびに、既存クラスとの組み合わせの数だけクラスが増えていきます。

Bridgeパターンは、「機能の階層」と「実装の階層」を、継承ではなくコンポジション(委譲)で結びつけることで、この組み合わせ爆発を防ぎます。

image.png

クラス構成

  • Abstraction(抽象化): 機能側の抽象クラス。実装側(Implementor)への参照を持つ
  • RefinedAbstraction(改善された抽象化): Abstractionを拡張したクラス
  • Implementor(実装者): 実装側の処理を定義するインターフェース
  • ConcreteImplementor(具体的な実装者): Implementorを実装する、具体的な実装クラス

image.png

Javaでの実装例

先ほどの「図形 × 描画方法」の例を、Bridgeパターンで実装してみます。

Implementor:描画方法側のインターフェース

public interface Renderer {
    void renderCircle(double radius);
    void renderRectangle(double width, double height);
}

ConcreteImplementor:具体的な描画方法

public class VectorRenderer implements Renderer {
    @Override
    public void renderCircle(double radius) {
        System.out.println("[ベクター描画] 半径" + radius + "の円を描画");
    }

    @Override
    public void renderRectangle(double width, double height) {
        System.out.println("[ベクター描画] " + width + "x" + height + "の四角形を描画");
    }
}

public class RasterRenderer implements Renderer {
    @Override
    public void renderCircle(double radius) {
        System.out.println("[ラスター描画] 半径" + radius + "の円をピクセルで描画");
    }

    @Override
    public void renderRectangle(double width, double height) {
        System.out.println("[ラスター描画] " + width + "x" + height + "の四角形をピクセルで描画");
    }
}

Abstraction:図形側の抽象クラス

public abstract class Shape {
    // 実装側(描画方法)への参照を持つ ← これが「ブリッジ」
    protected final Renderer renderer;

    protected Shape(Renderer renderer) {
        this.renderer = renderer;
    }

    public abstract void draw();
}

RefinedAbstraction:具体的な図形

public class Circle extends Shape {
    private final double radius;

    public Circle(Renderer renderer, double radius) {
        super(renderer);
        this.radius = radius;
    }

    @Override
    public void draw() {
        renderer.renderCircle(radius); // 実際の描画処理は、実装側に委譲する
    }
}

public class Rectangle extends Shape {
    private final double width;
    private final double height;

    public Rectangle(Renderer renderer, double width, double height) {
        super(renderer);
        this.width = width;
        this.height = height;
    }

    @Override
    public void draw() {
        renderer.renderRectangle(width, height);
    }
}

利用側のコード

public class Main {
    public static void main(String[] args) {
        Shape vectorCircle = new Circle(new VectorRenderer(), 5.0);
        Shape rasterRectangle = new Rectangle(new RasterRenderer(), 10.0, 20.0);

        vectorCircle.draw();
        rasterRectangle.draw();

        // 同じCircleクラスのまま、描画方法だけ差し替えることもできる
        Shape rasterCircle = new Circle(new RasterRenderer(), 5.0);
        rasterCircle.draw();
    }
}

実行結果:

[ベクター描画] 半径5.0の円を描画
[ラスター描画] 10.0x20.0の四角形をピクセルで描画
[ラスター描画] 半径5.0の円をピクセルで描画

Circleクラスは「円である」という情報だけを持ち、「どう描画するか」はRenderer側に委譲しています。

図形の種類が増えても描画方法のクラスは増えず、描画方法が増えても図形のクラスは増えません。

それぞれの階層が独立して拡張できるため、「図形3種類 × 描画方法3種類」でも、必要なクラス数は3+3(+α)で済み、9個の組み合わせクラスを用意する必要はありません。

Adapterパターンとの違い

BridgeはAdapterと構造が似ていますが、意図(いつ設計するか)が異なります。

Adapterは「既存の、変更できない異なるインターフェースを、後から繋ぎ合わせる」ための後付けの解決策であるのに対し、Bridgeは「最初から機能と実装を分離しておく」という、設計段階での意図的な構造です。

image.png

使用場面

  • 「機能」と「実装」という2つの独立した軸があり、それぞれを独立に拡張したい場合
  • 特定のプラットフォームやOS、DBの種類ごとに実装を切り替えたいが、上位の機能ロジックは共通化したい場合(例: GUIツールキットのウィジェットと、OSごとの描画エンジン)
  • 継承によるクラス階層が深くなりすぎて、組み合わせのたびにクラスが増えてしまっている場合

まとめ

項目 内容
解決する課題 「機能」と「実装」という独立した2軸を継承で表現すると、クラスが組み合わせ爆発してしまう
実現方法 機能側のAbstractionが、実装側のImplementorをフィールドとして持ち、処理を委譲する
メリット 機能・実装それぞれの階層を独立して拡張できる
Adapterとの違い Adapterは既存クラスの後付け接続、Bridgeは設計段階での意図的な分離

おすすめ書籍

GoFの23パターンをより深く学びたい方には、以下の書籍がおすすめです。
Java言語のサンプルコードとUMLを使い、初心者にもわかりやすく解説されています。

Java言語で学ぶデザインパターン入門 第3版 [ 結城 浩 ]

※本リンクはアフィリエイトリンクを含みます。

参考

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?