広告

Java : Bridge パターン (図解/デザインパターン)

Bridge パターンとは、GoF によって定義されたデザインパターンの1つです。
複雑になる継承関係を、委譲を使ってシンプルにする設計です。

本記事では、Bridge パターンを Java のコード付きで解説していきます。


デザインパターン(GoF) 関連記事

概要

Bridge パターン(ブリッジ・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義されたデザインパターンの1つである。 「橋渡し」のクラスを用意することによって、クラスを複数の方向に拡張させることを目的とする。

Bridge パターンとは、

  • 複雑になる継承関係を、委譲 を使ってシンプルにする

という設計です。

Bridge は、日本語的に発音すると「ブリッジ」となります。
意味は「橋」ですね。


【Bridge パターンのイメージ】

イメージ図1

ベースとなる図形クラスがあり、そのサブクラスとして

  • 長方形
  • 円形

があります。

さらに、図形の色として

  • 赤色
  • 緑色

があるとしましょう。

今回、色についても継承で実現しています。
これは少し過剰だとは思いますが、あくまで例ということでご容赦ください…

さて、しばらくして「青色」を追加することになりました。
クラス図は次のようになります。

イメージ図3

色を1つ追加するだけで、新しいクラスが 2つ 増えました。
さらに、図形として「三角形」を追加してみましょう。

イメージ図4

今度は、図形を1つ追加するだけで新しいクラスが 4つ 増えました。
これでは、この図形クラスの 拡張性は悪い と言ってよいでしょう。

このような問題を解決するのが Bridge パターンです。


イメージ図2

クラス数も減り、継承関係もすっきりしました。

継承関係は「図形」と「色」の2つに分離しています。
そして、その2つをつなぐ Bridge(橋) が 委譲 というわけですね。

構造は Strategy パターン とよく似ています。
… というかほぼ同じです。

個人的には、Bridge パターンと Strategy パターンはわざわざ別パターンにしなくてもよいのではないかな … とも思います。

厳密には違いはあるのでしょう。Bridge は 構造 に主眼を置き、Strategy は 振る舞い に主眼を置きます。
ただ、その線引きは難しいのかな、と思います。

Bridge パターンがいまいち理解できない場合は、Strategy パターンを先に学んでみるのもよいかもしれません。

関連記事:


クラス図とシーケンス図

クラス図1

シーケンス図1

上の図は、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 クラスを考えてみます。

クラス図2

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();

// 結果
// ↓
//-- 図形描画 --
//円形 : 緑色

結果は問題なしですね。
ただし、このクラス構成はとても 拡張性が悪い です。

概要 のところでも述べましたが、例えば …

  • 新しい図形を追加 (三角形など)
  • 新しい色を追加 (青色など)

と拡張していくと、すごい勢いでクラスが増えていきます。

クラス図3

このクラス構成の問題は、

  • 図形 : 長方形、円形
  • 色 : 赤色、緑色

という 2つの構造 を、無理に 1つの継承関係 にまとめてしまったのが原因です。


ノート

  • 多重継承が許されているプログラミング言語であれば、もう少しスマートな構成になるかもしれません。
    ただし、その場合は多重継承特有の問題に注意する必要があります。
  • Java 言語では多重継承が許されていないので、ここでは割愛します。

Bridge パターンを使うケース

それでは、Bridge パターンを使って、先ほどの構成 を改善してみましょう。

クラス図4

クラス構成を、

  • 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つずつ追加するだけで済みます。

クラス図5

まとめ

Bridge パターンとは、

  • 複雑になる継承関係を、委譲 を使ってシンプルにする

という設計です。

Strategy パターン ともよく似ているので、そちらも合わせて学習することをおすすめします。


関連記事

ページの先頭へ