デザインパターン入門 | Bridge(ブリッジ)パターン
「Bridge(ブリッジ)パターン」は、「機能の拡張」と「機能の実装」を分離し、クラスを作成するデザインパターンです。
「委譲」を利用したクラス関係を作り、「委譲」によってそれぞれのクラスを橋渡し(ブリッジ)していきます。
それでは、「Bridge(ブリッジ)パターン」の詳細について見ていきましょう。
Bridge(ブリッジ)パターンの理解に必要な「委譲」とは?
「委譲」は、あるクラスのフィールドに、あるクラスのインスタンスを持たせることで実現することができます。
まず、下記の図を見てましょう。
「Hoge」クラスには、Fugaクラス型のフィールド「fuga」があります。
この「fuga」フィールドには、「Fuga」クラスのインスタンスが入ります。
もし「Hoge」クラスから「Fuga」クラスの「methodA」メソッドを呼び出す場合は、「fuga」フィールドを介して「methodA」メソッドを呼び出すことができます。
「委譲」の理解は「Bridge(ブリッジ)パターン」を理解する上で大切な部分ですので、どのような仕組みになっているのかを理解していきましょう。
「機能の拡張」と「機能の実装」の違い
機能の拡張
「機能の拡張」とは、「継承」を行い、サブクラスに機能を追加し、「機能の拡張」を行うことです。
普段プログラムを書いている方なら日常的に利用しているオーソドックスな使い方ですね。
親クラスの機能はそのまま使わせてもらい、新たに継承したサブクラスに、機能を追加していきます。
機能の実装
機能の実装は、「抽象クラス」を親クラスとして継承し、「抽象」クラスで定義されている「抽象」メソッドについて、サブクラスで「処理の実装」を行うことを表します。
「抽象」クラスで定義されている「抽象」メソッドは、処理の内容をサブクラスで定義しないと「サブクラスのインスタンスを作る」ことができないため、サブクラスをインスタンス化するために「機能の実装」が必要となります。
Bridge(ブリッジ)パターンの事例と仕組み
今回は「飛行機」をテーマに「Bridge(ブリッジ)パターン」の事例についてご説明をしていきたいと思います。
これまでに学んだ「委譲」「機能の拡張」「機能の実装」がどのように組み合わされて、「Bridge(ブリッジ)パターン」が作られているのかに注目してみてください。
「機能の拡張」の「Plane」クラスは、「飛行機」を表す親クラスですが、飛行機の基本機能について定義しています。
package SampleBridge; public class Plane { protected ImplementPlane imPlane; public Plane(ImplementPlane imPlane) { this.imPlane = imPlane; } // 離陸 public void takeOff() { imPlane.takeOff(); } // 飛行 public void flying() { imPlane.flying(); } // 着陸 public void landing() { imPlane.landing(); } // 目的地へ行く public void goToTheDest() { takeOff(); flying(); landing(); } }
「Planeクラス」には、
- takeOff
- 離陸するためのメソッド
- flying
- 飛行するためのメソッド
- landing
- 着陸するためのメソッド
- goToTheDest
- 目的地へ向かうためのメソッド
が定義されています。
どんな飛行機でも必ず備わっている機能が定義されていますね。
「ImplementPlane」型の「imPlane」フィールドは、「委譲」を行うためのフィールドです。
そして、「JetPlane」クラスは、「ジェット飛行機」を表すクラスですが、このクラスは親クラスに「Plane」クラスを継承しています。
package SampleBridge; public class JetPlane extends Plane { public JetPlane(ImplementPlane imPlane) { super(imPlane); } // 逆推力装置を使う public void thrustReverse() { System.out.println("スラストリバースを行い減速しました。"); } }
「JetPlane」クラスが定義されているのは、
- thrustReverse
- 逆推力装置を使って飛行機を減速させるメソッド
が定義されています。
「スラストリバース」はジェットエンジン特有の機能のため、「JetPlane」クラスに定義されています。
詳しく知りたい方は、
を参照してみてください。
ジェットエンジンのカバーが開いてスラストリバースしている様子を見ることができます。
次にもう一つのサブクラスである「ReciproPlane」クラスは、「レシプロ(プロペラ)飛行機」を表すクラスです。
package SampleBridge; public class ReciproPlane extends Plane{ public ReciproPlane(ImplementPlane imPlane) { super(imPlane); } }
レシプロ機に何か特有の機能があればここに機能を追加していきます。(飛行機の専門家ではないため、思いつきませんでしたが・・・)
他にも飛行機の種類が増えた場合は、「Plane」クラスを継承したサブクラスを作ることで、機能の拡張を行うことができます。
そして、「機能の実装」を行っているのは、「ImplementPlane」抽象クラスと「ConcretePlane」クラスです。
「ImplementPlane」抽象クラスには、「takeOff・flying・landing」の3つの抽象メソッドが定義されています。
package SampleBridge; public abstract class ImplementPlane { public abstract void takeOff(); public abstract void flying(); public abstract void landing(); }
この抽象クラスを継承して「takeOff・flying・landing」抽象メソッドの内容を実装しているのが「ConcretePlane」クラスです。
package SampleBridge; public class ConcretePlane extends ImplementPlane{ private boolean isUseILS = true; @Override public void takeOff() { System.out.println("離陸しました。"); } @Override public void flying() { System.out.println("目的地へ飛行しています。"); } @Override public void landing() { if( isUseILS ) { useILS(); } System.out.println("着陸しました。"); } public void useILS(){ System.out.println("計器着陸装置を使用し着陸します。"); } public void changeUseILS() { isUseILS = !isUseILS; } }
「useILS」は「ILS (Instrument Landing System)」と呼ばれる無線を使った着陸装置を使う場合に実行するメソッドです。(話が専門的になるため、ILSの詳細は割愛)
「changeUseILS」メソッドはILSの利用可否を決定している「isUseILS」フィールドの値を切り替えるためのメソッドです。
これで必要なクラスは全て揃いました。
次に「委譲」を利用した「main」メソッドは下記のようになります。
package SampleBridge; public class Main { public static void main(String[] args) { System.out.println("【ジェットエンジン飛行機】"); JetPlane jetPlane = new JetPlane(new ConcretePlane()); jetPlane.goToTheDest(); jetPlane.thrustReverse(); System.out.println(""); System.out.println("【レシプロ飛行機】"); ReciproPlane reciproPlane = new ReciproPlane(new ConcretePlane()); ConcretePlane cp = (ConcretePlane)reciproPlane.imPlane; cp.changeUseILS(); reciproPlane.goToTheDest(); } }
このコードの中で注目したいのは、
ConcretePlane cp = (ConcretePlane)reciproPlane.imPlane; cp.changeUseILS();
の部分です。
「ConcretePlane」で実装しているメソッドを利用するために「委譲」の仕組みが利用されています。
実行結果は、下記のようになります。
【ジェットエンジン飛行機】 離陸しました。 目的地へ飛行しています。 計器着陸装置を使用し着陸します。 着陸しました。 スラストリバースを行い減速しました。 【レシプロ飛行機】 離陸しました。 目的地へ飛行しています。 着陸しました。
このように「委譲」という「橋(Bridge)」を架けることで、「機能の拡張」と「機能の実装」のクラスを橋渡しすることができます。
「機能の実装」クラスを切り替えることで、サブクラスの機能を切り替えることができるなど、必要な処理ごとに「機能の実装」クラスを作成することで、さまざまなサブクラスを作ることができます。
少し複雑に感じるかもしれませんが、ぜひご自身のプログラミングに「Bridge(ブリッジ)パターン」を活かしてみてはいかがでしょうか。