Java : Bridge パターン (図解/デザインパターン)
Bridge パターンとは、GoF によって定義されたデザインパターンの1つです。
 複雑になる継承関係を、委譲を使ってシンプルにする設計です。
本記事では、Bridge パターンを Java のコード付きで解説していきます。
デザインパターン(GoF) 関連記事
- 生成に関するパターン
 
- 振る舞いに関するパターン
 
概要
Bridge パターン(ブリッジ・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義されたデザインパターンの1つである。 「橋渡し」のクラスを用意することによって、クラスを複数の方向に拡張させることを目的とする。
Bridge パターンとは、
- 複雑になる継承関係を、委譲 を使ってシンプルにする
 
という設計です。
Bridge は、日本語的に発音すると「ブリッジ」となります。
 意味は「橋」ですね。
【Bridge パターンのイメージ】
ベースとなる図形クラスがあり、そのサブクラスとして
- 長方形
 - 円形
 
があります。
さらに、図形の色として
- 赤色
 - 緑色
 
があるとしましょう。
今回、色についても継承で実現しています。
 これは少し過剰だとは思いますが、あくまで例ということでご容赦ください…
さて、しばらくして「青色」を追加することになりました。
 クラス図は次のようになります。
色を1つ追加するだけで、新しいクラスが 2つ 増えました。
 さらに、図形として「三角形」を追加してみましょう。
今度は、図形を1つ追加するだけで新しいクラスが 4つ 増えました。
 これでは、この図形クラスの 拡張性は悪い と言ってよいでしょう。
このような問題を解決するのが Bridge パターンです。
クラス数も減り、継承関係もすっきりしました。
継承関係は「図形」と「色」の2つに分離しています。
 そして、その2つをつなぐ Bridge(橋) が 委譲 というわけですね。
構造は Strategy パターン とよく似ています。
 … というかほぼ同じです。
個人的には、Bridge パターンと Strategy パターンはわざわざ別パターンにしなくてもよいのではないかな … とも思います。
厳密には違いはあるのでしょう。Bridge は 構造 に主眼を置き、Strategy は 振る舞い に主眼を置きます。
 ただ、その線引きは難しいのかな、と思います。
Bridge パターンがいまいち理解できない場合は、Strategy パターンを先に学んでみるのもよいかもしれません。
関連記事:
クラス図とシーケンス図
上の図は、Bridge パターンの一般的なクラス図とシーケンス図です。
 クラス関係はとてもシンプルですね。
それでは、このクラス図をそのままコードにしてみましょう。
まずは Implementor インタフェースと、その実装である ConcreteImplementor クラスです。
public interface Implementor {
    void implementation();
}
public class ConcreteImplementor implements Implementor {
    @Override
    public void implementation() {
        System.out.println("Implementation!");
    }
}
 
      次に Abstraction クラスです。
 Implementor オブジェクトをフィールドに持つのがポイントですね。
public class Abstraction {
    private final Implementor impl;
    public Abstraction(Implementor impl) {
        this.impl = impl;
    }
    public void function() {
        System.out.print("function -> ");
        impl.implementation();
    }
}
 
      最後に、Abstraction のサブクラスである RefinedAbstraction です。
 もし RefinedAbstraction が必要なければ作らなくても問題ありません。
public class RefinedAbstraction extends Abstraction {
    public RefinedAbstraction(Implementor impl) {
        super(impl);
    }
    public void refinedFunction() {
        System.out.println("refinedFunction!");
    }
}
 
      それでは、これらのクラスを使ってみましょう。
final var abstraction = new RefinedAbstraction(new ConcreteImplementor());
abstraction.function();
abstraction.refinedFunction();
// 結果
// ↓
//function -> Implementation!
//refinedFunction!
 
      結果も問題なしですね。
具体的な例
Bridge パターンを使わないケース
もう少し具体的な例も見てみましょう。
 図形を描画する Shape クラスを考えてみます。
Shape クラスのサブクラスとして、具体的な図形である
- Rect クラス : 長方形
 - Circle クラス : 円形
 
があります。
さらに、図形の色を表現するために
- RedRect, RedCircle クラス : 赤色
 - GreenRect, GreenCircle クラス : 緑色
 
があります。
 (継承で色を表現するのは少しやりすぎ感はありますが、あくまで例ということで…)
それでは Shape クラスから見てみましょう。
Shape クラスは draw メソッドで図形を描画します。
 本来はキャンバスなどに描画したいのですが、今回は単純にテキストで出力します。
public class Shape {
    public void draw() {
        System.out.println("-- 図形描画 --");
    }
}
 
      次に、サブクラスとして
- Rect クラス : 長方形
 - Circle クラス : 円形
 
を作ります。
public class Rect extends Shape {
    @Override
    public void draw() {
        super.draw();
        System.out.print("長方形");
    }
}
public class Circle extends Shape {
    @Override
    public void draw() {
        super.draw();
        System.out.print("円形");
    }
}
 
      さらに、色を表現するサブクラスを作ります。
public class RedRect extends Rect {
    @Override
    public void draw() {
        super.draw();
        System.out.println(" : 赤色");
    }
}
public class GreenRect extends Rect {
    @Override
    public void draw() {
        super.draw();
        System.out.println(" : 緑色");
    }
}
public class RedCircle extends Circle {
    @Override
    public void draw() {
        super.draw();
        System.out.println(" : 赤色");
    }
}
public class GreenCircle extends Circle {
    @Override
    public void draw() {
        super.draw();
        System.out.println(" : 緑色");
    }
}
 
      それでは、これらのクラスを使ってみましょう。
final var redRect = new RedRect();
redRect.draw();
// 結果
// ↓
//-- 図形描画 --
//長方形 : 赤色
final var greenRect = new GreenRect();
greenRect.draw();
// 結果
// ↓
//-- 図形描画 --
//長方形 : 緑色
final var redCircle = new RedCircle();
redCircle.draw();
// 結果
// ↓
//-- 図形描画 --
//円形 : 赤色
final var greenCircle = new GreenCircle();
greenCircle.draw();
// 結果
// ↓
//-- 図形描画 --
//円形 : 緑色
 
      結果は問題なしですね。
 ただし、このクラス構成はとても 拡張性が悪い です。
概要 のところでも述べましたが、例えば …
- 新しい図形を追加 (三角形など)
 - 新しい色を追加 (青色など)
 
と拡張していくと、すごい勢いでクラスが増えていきます。
このクラス構成の問題は、
- 図形 : 長方形、円形
 - 色 : 赤色、緑色
 
という 2つの構造 を、無理に 1つの継承関係 にまとめてしまったのが原因です。
ノート
- 多重継承が許されているプログラミング言語であれば、もう少しスマートな構成になるかもしれません。
ただし、その場合は多重継承特有の問題に注意する必要があります。 - Java 言語では多重継承が許されていないので、ここでは割愛します。
 
Bridge パターンを使うケース
それでは、Bridge パターンを使って、先ほどの構成 を改善してみましょう。
クラス構成を、
- Shape (図形) : Rect、Circle
 - Color (色) : Red、Green
 
の2つに分離します。
 そして、その2つをつなぐ Bridge(橋) が 委譲 ですね。
まずは Color インタフェースとその実装を見てみましょう。
public interface Color {
    void drawColor();
}
public class Red implements Color {
    @Override
    public void drawColor() {
        System.out.println("赤色");
    }
}
public class Green implements Color {
    @Override
    public void drawColor() {
        System.out.println("緑色");
    }
}
 
      次に、Shape クラスとそのサブクラスです。
 Shape クラスのフィールドに、Color オブジェクトを持つのがポイントです。
public class Shape {
    protected final Color color;
    public Shape(Color color) {
        this.color = color;
    }
    public void draw() {
        System.out.println("-- 図形描画 --");
    }
}
public class Rect extends Shape {
    public Rect(Color color) {
        super(color);
    }
    @Override
    public void draw() {
        super.draw();
        System.out.print("長方形 : ");
        color.drawColor();
    }
}
public class Circle extends Shape {
    public Circle(Color color) {
        super(color);
    }
    @Override
    public void draw() {
        super.draw();
        System.out.print("円形 : ");
        color.drawColor();
    }
}
 
      それでは、これらのクラスを実行してみましょう。
final var redRect = new Rect(new Red());
redRect.draw();
// 結果
// ↓
//-- 図形描画 --
//長方形 : 赤色
final var greenCircle = new Circle(new Green());
greenCircle.draw();
// 結果
// ↓
//-- 図形描画 --
//円形 : 緑色
 
      結果も問題なしですね。
拡張性についても、例えば …
- 新しい図形を追加 (三角形など)
 - 新しい色を追加 (青色など)
 
としても、それぞれクラスを1つずつ追加するだけで済みます。
まとめ
Bridge パターンとは、
- 複雑になる継承関係を、委譲 を使ってシンプルにする
 
という設計です。
Strategy パターン ともよく似ているので、そちらも合わせて学習することをおすすめします。
関連記事
- 標準APIにならう命名規則
 - コメントが少なくて済むコードを目指そう
 - シングルトン・パターンの乱用はやめよう
 - メソッドのパラメータ(引数)は使う側でチェックしよう
 - 不変オブジェクト(イミュータブル) とは
 - 依存性の注入(DI)をもっと気軽に
 - 不要になったコードはコメントアウトで残さずに削除しよう
 - 簡易的な Builder パターン
 - 読み取り専用(const) のインタフェースを作る
 
- 図解/デザインパターン一覧 (GoF) 
        
- Abstract Factory パターン
 - Adapter パターン
 - Builder パターン
 - Chain of Responsibility パターン
 - Command パターン
 - Composite パターン
 - Decorator パターン
 - Facade パターン
 - Factory Method パターン
 - Flyweight パターン
 - Interpreter パターン
 - Iterator パターン
 - Mediator パターン
 - Memento パターン
 - Observer パターン
 - Prototype パターン
 - Proxy パターン
 - Singleton パターン
 - State パターン
 - Strategy パターン
 - Template Method パターン
 - Visitor パターン
 
 



