からくりがてんこ

IT関連情報、プログラミングに関する作業ログや備忘録を記載していきます。

C言語からの本当のJava言語へ (Bridge)

今回は、インターフェースの使い方について「なるほどな!!」と思ったパターン、Bridgeパターンを紹介します。私の印象として横に広がるクラス間連携のようなイメージです。あくまでイメージです。

これもまた便利でカッコイイパターンなので、知らない方は是非覚えてみて下さい。

Bridgeパターン

Bridgeパターンを紹介する例として、英語日本語一行表示する簡単なプログラムを考えてみましょう。

どちらの言語も一行表示するという方法は同じなので、親クラスとして言語表示クラスを定義します。
するとこのような感じになりますね。
(ここでは言語表示クラスを抽象クラスとします)

<<言語表示クラス>>

// 言語表示クラス
public abstract class DisplayLangage {

    // 一行表示するメソッド
    public void display() {
        // 抽象メソッドを呼び出して一行表示
        System.out.println(this.content());
    }
    
    // 表示内容を返すメソッド
    protected abstract String content();
}

<<日本語クラス>>

// 日本語クラス
public class Japanese extends DisplayLangage {
    @Override
    protected String content() {
        return "これは日本語です。";
    }
}

<<英語クラス>>

// 英語クラス
public class English extends DisplayLangage {
    @Override
    protected String content() {
        return "This is English.";
    }
}

上記をクラス図に表すとこうなります。
<<クラス図>>
f:id:ayumu-homes:20140128115514p:plain

もし、中国語表示の仕様追加が入った場合、当然、言語クラスを継承した中国語クラスを作成するというのが自然の流れでしょう。ここまでは大丈夫かと思います。

<<クラス図>>
f:id:ayumu-homes:20140128115754p:plain



さて、ここで機能拡張として二行同じ内容を表示する機能もほしいと要求があった場合どうしますか?




考えてみましょう。


言語表示クラスを継承した二行表示クラスを作成して、各種言語クラスをコピーする方法を考えるとこうなります。

<<クラス図>>
f:id:ayumu-homes:20140128120642p:plain


これは…あまりスマートではないですね。
いえ、全然スマートではないですね。
各言語クラスのメソッドはコピーで作られることになりますし、コピペで増やしていくのは色々とリスクを伴います。


こういう2つの拡張要素を持ったクラスが関連する時にブリッジパターンがとても有効です。
ブリッジとはご存知 ”橋” な訳ですが、何の橋か。



それは機能と機能の間の ”橋” となります。

ここでは、一行表示や二行表示といった「表示の機能」と日本語や英語などの「言語表現の機能」があります。
なので、表示機能と言語機能とを分けてしまいましょう。


一気に行ってしまいますが、分けた形がこうなります。

// ■表示クラス
public class Display {
    // 言語インターフェースを実装したオブジェクトを保持する領域
    protected Language langObject;
    
    // コンストラクタ
    public Display(Language langObject) {
        // 呼出し元で生成された言語オブジェクトを保持する
        this.langObject = langObject;
    }
    // 一行表示するメソッド
    public void display() {
        // 保持している言語オブジェクトに処理を委譲し、一行表示
        System.out.println(this.langObject.content());
    }
}


// ■表示拡張クラス
public class DisplayExt extends Display {
    // コンストラクタ
    public DisplayExt(Language langObject) {
        // 親クラスのコンストラクタを呼出し、言語オブジェクトを設定する
        super(langObject);
    }
    // 二行表示するメソッド
    public void display() {
        // 保持している言語オブジェクトに処理を委譲し、二行表示
        System.out.println(super.langObject.content());
        System.out.println(super.langObject.content());
    }
}


// ■言語インターフェース
public interface Language {
    public String content();
}


// ■日本語クラス
public class Japanese implements Language {
    @Override
    public String content() {
        return "これは日本語です。";
    }
}


// ■英語クラス
public class English implements Language {
    @Override
    public String content() {
        return "This is English.";
    }
}


<<クラス図>>
f:id:ayumu-homes:20140128123329p:plain


表示の機能」と「言語表現の機能」が綺麗に分かれて橋で繋がってますね。


注目は、言語インターフェースの部分になっている所と、表示クラスで言語インターフェースを実装した言語オブジェクトを保持するメンバ変数があるところです。
表示クラスで表示メソッドを実行する時、保持している言語オブジェクトの処理を実行しています。
この、他の人に処理をお願いすることを「処理を委譲する」と言います。


改めてクラス図を確認してみて下さい。
例えば、出力結果を

これは日本語です。これは日本語です。

のようにしたい場合、どのクラスを追加で増やせばよいか分かるのではないでしょうか。
そうです。「表示の機能」の表示クラスを継承した拡張クラスを作成すればよいのです。

機能毎に分けて橋で繋ぐという作業を行った結果かなりすっきりしたクラス図になりました。
その為「表示する方法」の追加や「言語表現方法」の追加の場合に機能拡張が大変楽になるということが分かって頂けたかと思います。




先ほども言いましたが、Bridgeパターンは2つの拡張要素を持ったクラスが関連する場合、非常に有効な手段となります。(また自分で設計できるとカッコイイと思ってしまいます。私だけかもですが)