Bridgeパターン


 ★★☆

Bridgeパターン

機能と実装を分離して、それぞれを独立に拡張する



Bridgeパターンとは、「Bridge」すなわち「橋」の役割を果たすパターンです。Bridgeパターンを利用することで、クラスの機能と実装を分離して、それぞれを独立に拡張することができるようになります。


例えば、文字列をすべて大文字や小文字で表示する2つのクラスがあります。また、文字列をそのまま [ ] で囲んで表示するクラスもあります。このとき、[ ] で囲んで大文字や小文字で表示できるようにしようと思ったら、[ ] で囲んで表示するクラスを継承した、大文字で表示するクラスと小文字で表示するクラスを追加する必要があります。


今回のように、大文字や小文字の2つのクラスだけなら手間はそんなにかかりませんが、場合によっては、何十というクラスを作成することが必要となります。Bridge パターンは、機能を拡張するための階層と実装を拡張するための階層を分離することにより、このようなわずらわしさを解消し、拡張を容易にするものです。


「継承」はクラスを拡張するために便利な方法ですが、クラス間の結びつきをがっちり固定してしまいます。ソースコード上に継承元となるクラスを記述して、そのクラスのサブクラスであると定義するからです。そして、この関係はソースコードを書き換えない限り変えることはできません。実行時に必要に応じてクラス間の関係を切り替えたいときには、継承を使うのは不適切です。したがって、このようなときには、「継承」ではなく「委譲」を使います。

「委譲」 とは、実行時に引き渡されたインスタンスを保持し、そのメソッドを呼び出すことです。つまり、 「仕事をしろ」といわれたら、そのインスタンスのメソッドに丸投げするわけです。


Bridgeパターンを使用すると、基本的な機能を実装するインタフェース(例えばAPI)と、アプリケーション側から利用しやすいインタフェース(例えば処理メニュー)を提供するクラスとを別々に用意し、これらをBridgeとして結合するようクラスライブラリを設計することにより、効率よく複数のプラットフォームに対応することができるはずです。もちろん、異なるOSで動作するアプリケーションを提供する場合にも使えるでしょう。

こういったアプリケーションにとって、基本となるクラスライブラリを提供するという機会は、ある程度経験を積んでからでないとないかもしれません。しかし、普段からこういった設計もできるようになっていないと、いざ実際の仕事で使用したいといった場合に余裕を持って対応することができないでしょう。そういった意味でこのパターンは、少々ハードルが高いかもしれませんが、知っておく価値のあるパターンです。



例題

文字列をすべて大文字にして出力するクラスとすべて小文字にして出力するクラスを作りなさい。

さらに、それらの前後を [ ] で囲むクラスも作りなさい。


実行結果
ABCDEFGH  
abcdefgh  
ABCDEFGH  
[ABCDEFGH]  









共通クラス

IDisplay.java
public interface IDisplay { public void display(String str); }
DisplayLowercase.java
public class DisplayLowercase implements IDisplay { public void display(String str) { System.out.print(str.toLowerCase()); } }
DisplayUppercase.java
public class DisplayUppercase implements IDisplay { public void display(String str) { System.out.print(str.toUpperCase()); } }

Bridgeパターンを使用しない例

AbstractDisplayOrnament.java
public abstract class AbstractDisplayOrnament implements IDisplay { public void displayOrnament(String str) { System.out.print("["); display(str); System.out.print("]"); } }
DisplayOrnamentLowercase.java
public class DisplayOrnamentLowercase extends AbstractDisplayOrnament { public void display(String str) { System.out.print(str.toLowerCase()); } }
DisplayOrnamentUppercase.java
public class DisplayOrnamentUppercase extends AbstractDisplayOrnament { public void display(String str) { System.out.print(str.toUpperCase()); } }
Main.java
public class Main { public static void main(String[] args) { IDisplay dispU = new DisplayUppercase(); dispU.display("AbCdEfGh"); System.out.println(); IDisplay dispL = new DisplayLowercase(); dispL.display("AbCdEfGh"); System.out.println(); AbstractDisplayOrnament dispOU = new DisplayOrnamentUppercase(); dispOU.display("AbCdEfGh"); System.out.println(); dispOU.displayOrnament("AbCdEfGh"); System.out.println(); } }

Bridgeパターンを使用した例

DisplayNormal.java
public class DisplayNormal { private IDisplay displayImplement; public DisplayNormal(IDisplay dispImp) { this.displayImplement = dispImp; } public void display(String str) { displayImplement.display(str); } }
DisplayOrnament.java
public class DisplayOrnament extends DisplayNormal { public DisplayOrnament(IDisplay dispImp) { super(dispImp); } public void displayOrnament(String str) { System.out.print("["); display(str); System.out.print("]"); } }
Main.java
public class Main { public static void main(String[] args) { DisplayNormal dispU = new DisplayNormal(new DisplayUppercase()); dispU.display("AbCdEfGh"); System.out.println(); DisplayNormal dispL = new DisplayNormal(new DisplayLowercase()); dispL.display("AbCdEfGh"); System.out.println(); DisplayOrnament dispOU = new DisplayOrnament(new DisplayUppercase()); dispOU.display("AbCdEfGh"); System.out.println(); dispOU.displayOrnament("AbCdEfGh"); System.out.println(); } }

大文字で表示する DisplayUppercase、小文字で表示する DisplayLowercase があるにもかかわらず、[] で囲む DisplayOrnamentUppercase や DisplayOrnamentLowercase の中にも大文字や小文字にする機能を作らなければなりません。

よって、(大文字小文字の2種類)×(修飾有り無しの2種類)分のクラスが必要になります。

Bridge パターンでは、実装の変更が考えられるメソッドに関しては、実装用のクラス階層に委譲するように設計します。実装用のクラス階層とは、ここでは display メソッドの実装を与えるクラス階層として、IDisplay インタフェースをインプリメントするクラス階層を考えます。具体的には、Display クラス、 IDisplay インタフェースをコーディング例のようにしておきます。

Display クラスは、コンストラクタで、引数とした渡された、大文字や小文字で表示するクラスのインスタンスをdisplayImplement に保存し、display メソッドで、そのインスタンスのdisplayメソッドを呼び出します。そして、[ ] で囲むクラスのスーパークラスとなります。

IDisplay は、大文字や小文字で表示するクラスに共通するインタフェースとなります。

そして、実際に大文字や小文字に変換する機能を実装する DisplayUppercase クラスと DisplayLowercase クラスを IDisplay インタフェースをインプリメントしたクラスとして作成するようにします。


このような設計にしておくと、機能を追加するために、 Display クラスを継承して作成した新しいクラスでも、すでに存在する実装部分を利用することができるようになります。例えば、 Display クラスを継承して、[]で囲んで表示できるようにした DisplayOrnament クラスです。すでに存在している、大文字や小文字で表示する機能を利用できるのです。

例えば、DisplayUppercase のインスタンスを引数に DisplayOrnament のインスタンスを生成します(displayImplement に DisplayUppercase のインスタンスが保存されます)。そして、そのインスタンスの displayOrnament メソッドを呼び出せば、“[” の表示に続いて、(DisplayOrnament のスーパークラスである Display の display メソッドが呼び出され、displayImplement に保存されているインスタンス、すなわち)DisplayUppercase のdisplay メソッドが呼び出され、文字列が大文字で表示されます。そして、“]” の表示がされます。


このように、機能を拡張するためのクラス階層と、実装を拡張するためのクラス階層を分けておくことで、実装階層クラスと機能拡張クラスを好みの組み合わせで利用することができるようになります。今回の例では、 IDisplay インタフェースと Display クラスが機能拡張クラス階層と実装拡張クラス階層を橋渡しする役目を果たしています。