daisukeの技術ブログ

AI、機械学習、最適化、Pythonなどについて、技術調査、技術書の理解した内容、ソフトウェア/ツール作成について書いていきます

Javaでデザインパターンを学ぶ:Template Methodパターン

Javaによるデザインパターンの2回目です!

教材は、参考文献の「Java言語で学ぶデザインパターン入門第3版」を使わせて頂きます。

今回は、Template Methodパターンです。1回目のシングルトンパターンに比べて、少し難しくなったと思います。

では、やっていきましょう!

参考文献

参考文献のサンプルプログラムのダウンロード

www.hyuki.com

はじめに

「Javaでデザインパターンを学ぶ」の記事一覧です。良かったら参考にしてください。

Javaでデザインパターンの記事一覧

先に、23種類のデザインパターンを示します。

参考サイト:デザインパターン (ソフトウェア) - Wikipedia

デザインパターン一覧
◆生成に関するパターン
 ・Abstract Factory:関連するインスタンスを状況に応じて、適切に生成する方法を提供する
 ・Builder:複合化されたインスタンスの生成過程を隠蔽する。
 ・Factory Method:生成されるインスタンスに依存しない、インスタンスの生成方法を提供する
 ・Prototype:同様のインスタンスを生成するために、原型のインスタンスを複製する
 ・Singleton:あるクラスについて、インスタンスが1つしか存在しないことを保証する
◆構造に関するパターン
 ・Adapter:元々関連性のない2つのクラスを接続するクラスを作る
 ・Bridge:クラスと呼び出し側の間の橋渡しをするクラスで、実装を隠蔽する
 ・Composite:再帰的な構造を表現する
 ・Decorator:あるインスタンスに対し、動的に付加機能を追加する
 ・Facade:複数のサブシステムの窓口となる共通のインタフェースを提供する
 ・Flyweight:多数のインスタンスを共有し、インスタンスの構築のための負荷を減らす
 ・Proxy:共通のインタフェースを持つインスタンスを内包し、利用者からのアクセスを代理する。Wrapperとも呼ばれる
◆振る舞いに関するパターン
 ・Chain of Responsibility:イベントの送受信を行う複数のオブジェクトを鎖状につなぎ、それらの間をイベントが渡されていくようにする
 ・Command:複数の異なる操作について、それぞれに対応するオブジェクトを用意し、オブジェクトを切り替えることで、操作の切り替えを実現する
 ・Interpreter:構文解析のために、文法規則を反映するクラス構造を作る
 ・Iterator:複数の要素を内包するオブジェクトのすべての要素に対して、順番にアクセスする方法を提供する
 ・Mediator:オブジェクト間の相互作用を仲介するオブジェクトを定義し、オブジェクト間の結合度を低くする
 ・Memento:データ構造に対する一連の操作のそれぞれを記録しておき、以前の状態の復帰または操作の再現が行えるようにする
 ・Observer:インスタンスの変化を他のインスタンスから監視できるようにする
 ・State:オブジェクトの状態を変化させることで、処理内容を変えられるようにする
 ・Strategy:データ構造に対して適用する一連のアルゴリズムをカプセル化し、アルゴリズムの切り替えを容易にする
 ・Template Method:アルゴリズムは抽象クラスで、処理内容はサブクラスで定義する
 ・Visitor:データ構造を保持するクラスと、それに対して処理を行うクラスを分離する

今回は、「Template Methodパターン」をやっていきます!

サンプルコードの理解

Template Methodパターンは、処理の流れは抽象クラスで定義しておき、各処理の具体的な内容は、それぞれのサブクラスで実装するデザインパターンです。

分かりやすい例がありました。

Template Method パターン - Wikipedia

文章を書く流れを、ヘッダ、アイテム(ボディ)、フッタと、抽象クラスで定義しておきます。

普通の文章のサブクラスでは、ヘッダ、アイテム(ボディ)、フッタを、通常の文章として、箇条書きに書いています。一方、HTMLのサブクラスでは、同じ流れを、HTMLの箇条書きの構文を使用して書いています。

処理の流れは同じだけど、それぞれの処理の内容が少しずつ違うような実装の場合に役立ちそうです。

今回のクラス図

PlantUMLで作成したクラス図のコードと画像ファイルです。

@startuml
abstract class AbstractDisplay {
    + {abstract} void open()
    + {abstract} void print()
    + {abstract} void close()
    + final void display()
}

class CharDisplay {
    - char ch
    + void open()
    + void print()
    + void close()
}
AbstractDisplay <|-- CharDisplay

class StringDisplay {
    - String string
    - int width
    - void printLine()
    + void open()
    + void print()
    + void close()
}
AbstractDisplay <|-- StringDisplay
@enduml

今回のクラス図
今回のクラス図

Mainクラス(使う側のクラス)

まずは、Template Methodパターンどう使われるのかを見るために、Mainクラスを見ていきます。

分かりやすくなると思うので、先に実行結果も示します。

public class Main {
    public static void main(String[] args) {
        // 'H'を持ったCharDisplayのインスタンスを1個作る
        AbstractDisplay d1 = new CharDisplay('H');

        // "Hello, world."を持ったStringDisplayのインスタンスを1個作る
        AbstractDisplay d2 = new StringDisplay("Hello, world.");

        // d1,d2とも、すべて同じAbstractDisplayのサブクラスのインスタンスだから
        // 継承したdisplayメソッドを呼び出すことができる
        // 実際の動作は個々のクラスCharDisplayやStringDisplayで定まる
        d1.display();
        d2.display();
    }
}

実行結果

実行結果
実行結果

Template Methodのクラスは、AbstractDisplay です。

ソースコードのコメントにもありますが、処理の流れは、AbstractDisplaydisplay() で定義されていて、各処理(下にソースを示します)は、CharDisplayStringDisplay で、それぞれ定義されているようです。

これだけでは分からないので、それぞれのソースコードを見ていきます。

AbstractDisplayクラス

Template Methodのクラスの AbstractDisplay です。

public abstract class AbstractDisplay {
    // open, print, closeはサブクラスに実装をまかせる抽象メソッド
    public abstract void open();
    public abstract void print();
    public abstract void close();

    // displayはAbstractDisplayで実装してるメソッド
    public final void display() {
        open();
        for (int i = 0; i < 5; i++) {
            print();
        }
        close();
    }
}

抽象クラスが定める処理の流れは、display() ですね。最初に open() をコールして、次は、print() を5回コールし、最後は、close() をコールしています。

display() は、'final'(オーバーライドできない)としているところが重要なようです。おそらく、もし、ここをサブクラスで上書きできてしまうと、サブクラスをさらに継承した孫クラスを作り、ここで display() に対して、処理順を変えてしまうような上書きをしてしまうと、正しく動かないものになってしまうからだと思います。

open()print()close() は、サブクラスで定義しているようです。

CharDisplayクラス

次は、CharDisplayクラスです。

public class CharDisplay extends AbstractDisplay {
    private char ch; // 表示すべき文字

    // コンストラクタ
    public CharDisplay(char ch) {
        this.ch = ch;
    }

    @Override
    public void open() {
        // 開始文字列として"<<"を表示する
        System.out.print("<<");
    }

    @Override
    public void print() {
        // フィールドに保存しておいた文字を1回表示する
        System.out.print(ch);
    }

    @Override
    public void close() {
        // 終了文字列として">>"を表示する
        System.out.println(">>");
    }
}

プライベートメンバ変数として、ch を持ち、コンストラクタでは、引数で ch を初期化しています。

open() では << を標準出力に表示し(改行は出力しない)、print() では ch を表示します。最後に、close() では >> を出力しています(改行も出力する)。

Mainクラスで、CharDisplayクラスのインスタンスを作るときに、引数に H を指定していました。よって、実行結果の通り、<<HHHHH>> と表示されたわけですね。なるほど。

StringDisplayクラス

次は、StringDisplayクラスです。

public class StringDisplay extends AbstractDisplay {
    private String string;  // 表示すべき文字列
    private int width;      // 文字列の表示幅

    // コンストラクタ
    public StringDisplay(String string) {
        this.string = string;
        this.width = string.length();
    }

    @Override
    public void open() {
        printLine();
    }

    @Override
    public void print() {
        System.out.println("|" + string + "|");
    }

    @Override
    public void close() {
        printLine();
    }

    // openとcloseから呼び出されて"+----+"という文字列を表示するメソッド
    private void printLine() {
        System.out.print("+");
        for (int i = 0; i < width; i++) {
            System.out.print("-");
        }
        System.out.println("+");
    }
}

プライベートメンバ変数として、stringwidth を持ち、コンストラクタでは、引数で string を初期化し、width はその長さで初期化しています。

open()close() は同じ実装になっており、+ を出力した後、string と同じ長さの - を出力しています。print() では | の後、string を表示し、最後に、| を表示しています。

Mainクラスで、StringDisplayクラスのインスタンスを作るときに、引数に Hello, world. を指定していました。よって、実行結果の通り、以下と表示されたわけです。

+++++++++++++++
|Hello, world.|
|Hello, world.|
|Hello, world.|
|Hello, world.|
|Hello, world.|
+++++++++++++++

おわりに

今回はTemplate Methodパターンを学びました。

似たような処理の流れのクラスは、Template Methodパターンを用いると、同じような実装が存在することを防ぐことができますね。また、先に処理の流れが決まっている場合に、間違いなく、それを守ることが実現できそうです。

今回は以上です!

最後までお読みいただき、ありがとうございました。