図1●サブクラスでdrawメソッドの機能を実装する
図1●サブクラスでdrawメソッドの機能を実装する
[画像のクリックで拡大表示]
図2●MyClassBクラスでも2通りにdrawメソッドを実装するのは面倒だ
図2●MyClassBクラスでも2通りにdrawメソッドを実装するのは面倒だ
[画像のクリックで拡大表示]

 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)アドバイザリースタッフ

表紙ページへ