23種類のGoFデザインパターンには、それぞれ親しみやすい名前が付けられています。今回紹介する2つのパターンの名前を直訳すると、「橋(Bridge)」および「装飾者(Decorator)」となります。どちらも構造に関するパターンに分類されているので、構造として橋を架けたような工夫、そして構造に飾りを付ける工夫であることが予測できます。オブジェクト指向プログラミングにおける橋と飾りとは、いったい何なんだろう...興味が湧いてきたところで本題に入りましょう。
【お役立ち度】★★★★★
●拡張から実装に橋を架けるBridgeパターン
継承元のクラスをスーパークラスと呼び、継承先のクラスをサブクラスと呼びます。サブクラスの役割には、スーパークラスの抽象メソッドを適切に実装することと、スーパークラスの機能を拡張することがあります。
ここで、引数に与えられたデータを描画するdrawメソッド(抽象メソッド)を持つMyClassAクラスがあるとしましょう。MyClassAクラスのサブクラスとして、MyDisplayAクラスとMyPrinterAクラスがあります。それぞれ、ディスプレイおよびプリンタにデータを描画するように、drawメソッドの処理内容を実装しています。UMLの表記では、サブクラスからスーパークラスに向けた白抜きの三角形で継承を表します(図1[拡大表示])。
MyClassAクラスの機能を拡張するために、新たなサブクラスMyClassBを作るとしましょう。ごく普通に継承しただけでは、MyDiplayAクラスとMyPrinterAクラスによるdrawメソッドの実装をMyClassBに引き継ぐことはできません。そのため、MyClassBクラスでも、drawメソッドを2通りに実装する必要があります。ちょっと面倒ですね。何かいい方法は、ないでしょうか? 図2[拡大表示]を見ながら考えてください。
MyDiplayBクラスやMyPrinterBクラスなど作らず、MyClassBクラスからMyClassAクラスへたどり、そこから橋を架けるようにMyDiplayAクラスやMyPrinterAクラスのdrawメソッドを利用できたら便利でしょう。そうです! それを実現するのが、Bridgeパターンなのです。
MyClassAクラスからdrawメソッドの定義を切り離し、MyInterfaceインターフェイスとしましょう。MyInterfaceインターフェイスを実装する形で、MyDisplayAクラスとMyPrinterAクラスを作成しておきます。MyClassAクラスには、MyInterfaceインターフェイスをデータ型としたbridgeフィールドを定義し、クラスを使う側のコードでMyDisplayAクラスまたはMyPrinterBクラスのオブジェクトを設定します。このbridgeフィールドが、橋の役割を果たすのです。MyClassAクラスには、通常のメソッド(抽象メソッドでないメソッド)として、drawメソッドを定義しておき、その処理としてbridgeフィールドを経由してMyDisplayAクラスまたはMyPrinterAクラスのdrawメソッドの実装を呼び出します(リスト1[拡大表示])。
リスト1●drawメソッドの実装へ橋を架ける
public class MyClassA { // 橋となるフィールド private MyInterface bridge; // drawメソッドの実装を呼び出す public void draw(int data) { bridge.draw(data); } ・・・ } |
これで、準備OKです。MyClassAクラスを拡張する形で継承するMyClassBクラスは、bridgeフィールドとdrawメソッドを引き継ぎます。そのため、bridgeフィールドの設定次第で、MyDisplayAクラスまたはMyPrinterAクラスのdrawメソッドの実装を呼び出せるのです。見事に橋が架かりました。星5つの満点です。
【お役立ち度】★★★★★
●オーバーライドせずに機能を追加するDecoratorパターン
「既存のクラスのメソッドに機能を追加するにはどうしたらよいか」という質問に対し、皆さんなら何と答えますか? オブジェクト指向プログラミング言語をきちんと学んだ人なら「既存のクラスを継承したサブクラスを作り、引き継いだメソッドの処理内容をオーバーライドする」と答えるでしょう。ピンポ~ン! 正解です。でも、継承以外にも手段があります。それが、Decoratorパターンです。
既存のMyClassAクラスのpublic void myMethod(int a)という構文のメソッドに、何らかの機能を追加するとしましょう。新たに作成するMyClassBクラス(MyClassAクラスを継承しません)のメンバとして、まったく同じ構文のmyMethodメソッドを定義し、その処理の中でMyClassAクラスのインスタンスを作成してmyMethodメソッドを呼び出せばよいのです。この呼び出しの前後に、任意の機能を追加できます。任意の機能でメソッドの呼び出しを飾る(Decorator)というわけです(リスト2[拡大表示])。
リスト2●既存のメソッドの呼び出しの前後に処理を追加する
public class MyClassB { // これはメソッドのオーバーライドではない public void myMethod(int a) { // メソッドの呼び出し前に機能を追加できる ・・・ // MyClassAクラスのmyMethodメソッドを呼び出す MyClassA obj = new MyClassA(); obj.myMethod(a); // メソッドの呼び出し後に機能を追加できる ・・・ } } |
クラスの利用者からは、MyClassBクラスのmyMethodメソッドを、MyClassAクラスのmyMethodメソッドと同様に呼び出せた方が便利です。そのために、まったく同じ構文で新しいmyMethodメソッドを定義するのです。これが、Decoratorパターンのポイントです。さらに使いやすくするために、myMethodメソッドの構文をインターフェイスとして定義しておくとよいでしょう。このインターフェイスを実装する形でMyClassAクラスとMyClassBクラスの両方を作成すれば、インターフェイスをデータ型とした参照を使って、どちらのクラスのmyMethodメソッドも呼び出せます。
高度なテクニックを学ぶことによって、当たり前の工夫を忘れてしまうことがあります。Decoratorパターンのアイディは、当たり前と言えば当たり前なのです。でも、継承を知ってしまうと、うっかり忘れてしまいがちです。そんな頭の固い人に「継承がすべてじゃないんですよ」と教えてくれたDecoratorパターンの評価は、星5つの満点としましょう。かく言う私も、頭の固い人の一人だからです。
次回は、CompositeパターンとProxyパターンを紹介します。
矢沢久雄 グレープシティ株式会社(http://www.grapecity.com)アドバイザリースタッフ |
表紙ページへ |