2010年5月23日日曜日

Bridgeパターン

Bridgeパターンとは、実装クラス層と機能クラス層を橋渡しするためのパターンです。

実装クラス層と言うのは、abstractなクラスやinterfaceとそれらを継承、実装したクラス群のことをいいます。機能クラス層は1つのベースクラスと、メソッド等を追加するためにベースクラスを継承したクラスが複数個含まれるクラス群のことです。

この2つの層を結ぶ橋を用意することで、クラスの拡張を容易にすることができます。

(そろそろクラス図がないと説明しづらくなってきましたが、お付き合いください)

今回の例は単純に、引数でもらった文字列を加工して表示するだけです(毎度おなじみですが)
まずは、機能クラス層のベースクラスDisplaySampleです。
public class DisplaySample {
    private DisplayImpl impl;

    public DisplaySample(DisplayImpl impl) {
        this.impl = impl;
    }

    public void print() {
        impl.print();
    }
}

フィールドとして実装クラス層のオブジェクトを持ちます。これが「橋」となります。メソッドとしてはprint()のみです。これを呼ぶと、実装クラス層で定義されているprint()を呼び出します。

このベースクラスを継承したクラス(機能追加したクラス)は以下のとおりです。単純に、printTwice()を追加しただけです。これを呼ぶと、実装クラス層のprint()を2回呼びます。
public class ExtendedDisplaySample extends DisplaySample {
    public ExtendedDisplaySample(DisplayImpl impl) {
        super(impl);
    }

    public void printTwice() {
        System.out.print("1回め:");
        print();
        System.out.print("2回め:");
        print();
    }
}

次に、実装クラス層です。実装クラス層では、インターフェースとその実装クラスがあるわけですが、インターフェースとしては以下の通りです。
public interface DisplayImpl {
    public void print();
}

実装クラスは今回は2つ用意しました。1つは以下のように引数の文字列にアスタリスクをつけて返すだけのprint()を実装したクラスです。
public class AsteriskDisplayImpl implements DisplayImpl {
    private String value;

    public AsteriskDisplayImpl(String value) {
        this.value = value;
    }

    public void print() {
        System.out.println("*** " + value + " ***");
    }
}

もう一つは文字列の大文字小文字を変換して表示するprint()を実装したクラスです。
public class UpperLowerDisplayImpl implements DisplayImpl {
    private String value;

    public UpperLowerDisplayImpl(String value) {
        this.value = value;
    }

    public void print() {
        // valueをchar配列に変換
        char[] valueChars = value.toCharArray();

        // 大文字小文字を変換した後の文字を格納するビルダー
        StringBuilder sb = new StringBuilder();

        // 1文字ずつ、大文字か小文字かを判別し、ビルダーに変換後の文字を
        // 格納していく
        for (char ch : valueChars) {
            if (Character.isLowerCase(ch)) {
                sb.append(Character.toUpperCase(ch));
            } else {
                sb.append(Character.toLowerCase(ch));
            }
         }

         // toString()を呼び出して文字列として表示
         System.out.println(sb.toString());
     }
}
よくよく考えると、わざわざchar配列を用意しなくても、ループ中にvalue.charAt()を使えばいいよねorz

Mainは以下のとおりです。
public class Main {
    public static void main(String[] args) {
        DisplaySample dis1 = new DisplaySample(new AsteriskDisplayImpl("asterisk"));
        DisplaySample dis2 = new DisplaySample(new UpperLowerDisplayImpl("upperLOWER"));
        ExtendedDisplaySample ext1 = new ExtendedDisplaySample(new AsteriskDisplayImpl("twice / asterisk"));
        ExtendedDisplaySample ext2 = new ExtendedDisplaySample(new UpperLowerDisplayImpl("twice / uPpErLoWeR"));

        dis1.print();
        dis2.print();
        ext1.printTwice();
        ext2.printTwice();
    }
}
Mainでは、全部で4パターン用意してます。DisplaySampleとAsterisk、DisplaySampleとUpperLower、ExtendedとAsaterisk、ExtendedとupperLowerです。実行結果は以下のとおりです。








このパターンを使うと、機能拡張をしたい場合はExtendedDisplaySample(場合によってはDisplaySampleを再び継承?)を継承したり、DisplayImplの実装クラスを増やせば良いだけですね。

関連パターン
Abstract Factoryパターン」interface実装クラスにこのパターンを用いることがある
Template Methodパターン」実装クラス層で利用
Adapterパターン」異なるクラス同士を結びつけるためのパターン。これも橋渡し役と言えそう

0 件のコメント:

コメントを投稿