【Java】Bridgeパターン【デザインパターン】
目次
Bridgeパターンについて
Bridgeパターンとは
- 機能のクラス階層と、実装のクラス階層を、橋渡しするパターンです
- 機能のクラス階層と実装のクラス階層が階層構造の中に混在しないように、2つの独立したクラス階層に分離して、それぞれの階層の橋渡しをします
機能のクラス階層
- クラスに新しい機能を追加したい場合は、クラス階層の中から、目的に近いクラスを探し、そのサブクラスを作成します
- スーパークラスでは基本的な機能を持っている
- サブクラスでは新しい機能を追加する
- 他の機能を追加する場合は、さらに新しいサブクラスを用意して、階層構造にします
実装のクラス階層
- 抽象メソッドでインターフェースを規定し、サブクラスで実装します
- 役割分担をすることで部品として交換可能性が高いクラスを作ることができます
- 新しいメソッドを追加するためではなく、役割分担のために、クラス階層を使います
- スーパークラスは抽象メソッドでインターフェースを規定する
- サブクラスは具象メソッドでインターフェースを実装する
サンプルプログラム
- Bridgeパターンを使って、抽象的な何かを表じする例を取り上げます
- 各クラスの役割は以下のようになっています
名前 | 役割 |
---|---|
Display (Abstraction) | <機能のクラス階層> 表示するクラス |
CountDisplay (RefinedAbstration) | <機能のクラス階層> 指定回数だけ表示する機能を追加したクラス |
DisplayImpl (Implementor) | <実装のクラス階層> 表示するクラス |
StringDisplayImpl (ConcreteImplementor) | <実装のクラス階層> 文字列を使って表示するクラス |
<機能のクラス階層> Displayクラス
- 抽象的な何かを表示するクラスで、機能のクラスの最上位にいるクラスです
- コンストラクタには、実装を表すクラスのインスタンスを渡します
- 引数で渡されたインスタンスはimplのフィールドに保存され、2つのクラス階層の橋になります
- open,print,closeメソッドは、Displayクラスのインターフェースで、implフィールドの実装メソッドを用います
- Displayのインターフェースが、DisplayImplに変換されていることになります
public class Display { private DisplayImpl impl; //実装を表すインスタンス public Display(DisplayImpl impl) { this.impl = impl; } public void open() { impl.rawOpen(); } public void print() { impl.rawPrint(); } public void close() { impl.rawClose(); } public final void display() { open(); print(); close(); } }
<機能のクラス階層> CountDisplayクラス
- Displayクラスの機能に、指定回数表示する、という機能を追加したクラスです
public class CountDisplay extends Display { public CountDisplay(DisplayImpl impl) { super(impl); } public void multiDisplay(int times) { // times回繰り返して表示する open(); for (int i = 0; i < times; i++) { print(); } close(); } }
<実装のクラス階層> DisplayImplクラス
- 実装のクラス階層の最上位に位置する抽象クラスです
- 抽象メソッドを3つ持ち、それぞれDisplayクラスのメソッド(open,print,close)に対応します
public abstract class DisplayImpl { public abstract void rawOpen(); public abstract void rawPrint(); public abstract void rawClose(); }
<実装のクラス階層> StringDisplayImplクラス
- 文字列を表示するクラスで、DisplayImplクラスのサブクラスとして抽象メソッドを実装します
public class StringDisplayImpl extends DisplayImpl { private String string; private int width; public StringDisplayImpl(String string) { this.string = string; this.width = string.getBytes().length; } public void rawOpen() { printLine(); } public void rawPrint() { System.out.println("|" + string + "|"); } public void rawClose() { printLine(); } private void printLine() { System.out.print("+"); for (int i = 0; i < width; i++) { System.out.print("-"); } System.out.println("+"); } }
Mainクラスで動作確認
- 4クラスを組み合わせて文字列を表示します
- Displayクラス、CountDisplayクラスも共に、StringDisplayImplクラスのインスタンスが実装を担っています
public class Main { public static void main(String[] args) { Display d1 = new Display(new StringDisplayImpl("Hello, Japan.")); //Displayクラスのインスタンス代入 Display d2 = new CountDisplay(new StringDisplayImpl("Hello, World.")); //CountDisplayクラスのインスタンス代入 CountDisplay d3 = new CountDisplay(new StringDisplayImpl("Hello, Universe.")); d1.display(); d2.display(); d3.display(); d3.multiDisplay(5); } }
Bridgeパターンのメリット
- 機能と実装のクラスを分けておくことで、それぞれのクラス階層を独立に拡張できます
- 機能を追加したい場合、機能のクラス階層にクラスを追加します。実装のクラス階層は変更する必要がありません
- しかも、追加した機能は全ての実装で使用できます
- クラス間が硬い結びつきになる"継承"ではなく、緩い結びつきになる、"委譲"を行います
- Abstraction (Display) クラスでは、implフィールドに実装のインスタンスを保持し、処理は実装クラスに任せて、たらい回しにしています
- 実装の変更を行いたい場合は、別のConcreteImplementerのインスタンスをAbstraction やRefinedAbstration クラスに渡したらいいので、Mainクラス以外変更する必要がありません
今日のポイント
- 機能のクラス階層と、実装のクラス階層を、橋渡しするパターンです
- 機能のクラス階層と実装のクラス階層が階層構造の中に混在しないように、2つの独立したクラス階層に分離して、それぞれの階層の橋渡しをします
- 機能と実装のクラスを分けておくことで、それぞれのクラス階層を独立に拡張できます
- 機能を追加する場合、機能のクラス階層にクラスを追加します。
- 実装のクラス階層は変更する必要がなく、追加した機能は全ての実装で使用できます
- 実装の変更を行いたい場合は、別のConcreteImplementerのインスタンスをAbstraction やRefinedAbstration クラスに渡すだけで良く、Mainクラス以外変更する必要がありません
- 機能を追加する場合、機能のクラス階層にクラスを追加します。
本日もお疲れ様です😊