Template Methodパターン


 ★★★

Template Methodパターン

スーパークラスで処理の枠組み、サブクラスで内容を定める

ソフトウェアを作っていると、何ヶ所かで同じような処理を記述しているのに気づくことがあります。そういった時は、同じような処理の部分を1つにまとめ、共通化することになります。

手続き型のプログラミング言語では、同じような処理の部分を1つの共通関数としてまとめて、異なる部分は、引数に応じた場合分けで対応したりします。

オブジェクト指向言語では、同じような処理の部分を1つのクラスとしてまとめます。そして、それを継承したサブクラスを作り、それぞれに異なる部分だけを実装します。この時、サブクラスごとに異なる部分は抽象メソッドとして、スーパークラスで空の定義をしておくことも多いです。この時、出来上がったクラスの構成は、Template Methodパターンの形になります。

将来の機能拡張に備えて、基本となるクラスでは積極的にTemplate Methodパターンを採用して、サブクラスを作った時にオーバーライドできるようなメソッドをたくさん用意しているケースも見かけます。

しかし、場合によっては、機能の拡張を継承ではなく委譲(インスタンスを生成しメソッド呼び出し)によって行う方が適しているケースもあります。そういったケースでは、予めTemplate Methodパターンを前提とした枠組みが組まれていると、かえって弊害となることもあります。



Template Methodパターンを使うと、アルゴリズムはスーパークラスに書かれていますので、サブクラスにいちいち記述する必要がなくなります。アルゴリズムにバグが見つかっても、スーパークラスさえ直せばよい、ということになります。反面、サブクラスからは、スーパークラスで宣言されているメソッドがどういうタイミングで呼び出されているかはわかりません。よって、各メソッドの呼び出される順番がわからないとサブクラスに実装できないというような場合には、スーパークラスのソースを見て、そのタイミングを確認しないと、サブクラスの実装が難しい場合もあります。


Template Methodパターンでは、アルゴリズムをスーパークラスに記述し、その中で呼ばれているメソッドの実装(メソッドの実際の処理内容)はサブクラスに記述します。どのレベルで処理を分けるか、その処理をスーパークラスに置き、どの処理をサブクラスに置くかについては、定まったマニュアルがあるわけではありません。それは、プログラムの設計を行う人に任されています。

スーパークラスの記述を多くすれば、サブクラスの記述は楽になりますが、自由度は減るといって良いでしょう。逆にスーパークラスの記述が少なくなれば、サブクラスの記述は大変になり、また個々のサブクラスで、処理が重複して記述されることになるかもしれません。


例題

与えられた文字を<<と>>で挟んで5回表示するクラス CharDisplay や文字列を枠で囲って5回表示するクラス StringDisplay を作りなさい。


実行結果
CharDisplay

<<AAAAA>>
    StringDisplay
+-------+
| Hello |
| Hello |
| Hello |
| Hello |
| Hello |
+-------+













Template Methodパターンを使用しない例

CharDisplay.java
public class CharDisplay { private char ch; public CharDisplay(char ch) { this.ch = ch; } public void display() { System.out.print("<<"); for (int i = 0; i < 5; i++) System.out.print(ch); System.out.println(">>"); } }
StringDisplay.java
public class StringDisplay { private String str; private int width; public StringDisplay(String str) { this.str = str; this.width = str.getBytes().length; } public void display() { printLine(); for (int i = 0; i < 5; i++) System.out.println("|" + str + "|"); printLine(); } private void printLine() { System.out.print("+"); for (int i = 0; i < width; i++) System.out.print("-"); System.out.println("+"); } }
Main.java
public class Main { public static void main(String[] args) { CharDisplay d1 = new CharDisplay('A'); d1.display(); StringDisplay d2 = new StringDisplay("Hello"); d2.display(); } }

Template Methodパターンを使用した例

AbstractDisplay.java
public abstract class AbstractDisplay { protected abstract void open(); protected abstract void print(); protected abstract void close(); public final void display() { open(); for (int i = 0; i < 5; i++) print(); close(); } }
CharDisplay.java
public class CharDisplay extends AbstractDisplay { private char ch; public CharDisplay(char ch) { this.ch = ch; } protected void open() { System.out.print("<<"); } protected void print() { System.out.print(ch); } protected void close() { System.out.println(">>"); } }
StringDisplay.java
public class StringDisplay extends AbstractDisplay { private String str; private int width; public StringDisplay(String str) { this.str = str; this.width = str.getBytes().length; } public void open() { printLine(); } public void print() { System.out.println("|" + str + "|"); } public void close() { printLine(); } private void printLine() { System.out.print("+"); for (int i = 0; i < width; i++) System.out.print("-"); System.out.println("+"); } }
Main.java
public class Main { public static void main(String[] args) { AbstractDisplay d = new CharDisplay('A'); d.display(); d = new StringDisplay("Hello"); d.display(); } }

CharDisplay クラスや StringDisplay クラスに、処理の枠組み(流れ)と処理内容が一緒に記述されてしまっています。

クラス処理内容
CharDisplay最初に <<
文字を5回
最後に >>
StringDisplay最初に +-----+
文字列を5回
最後に +-----+

この場合、もし処理の枠組みが変更になり、表示は10回にするとか、最後の表示はいらないとかになったらどうでしょう。

すべてのクラス(この例では2つしかないですが)を変更しなければならなくなります。

処理の枠組みは、スーパークラスの AbstractDisplay の display() メソッドに記述されています。

最初に open()、次にprint()を5回、最後にclose() を呼ぶというものです。

そして、AbstractDisplay を継承したサブクラス CharDisplay、StringDisplay には処理の枠組みはなく、それぞれ open() では何をする、print() では何をする、close() では何をするということが書かれているのみです。

よって、もし処理の枠組みが変更になり、表示は10回にするとか、最後の表示はいらないとかになっても、処理の枠組みを記述した AbstractDisplay のみを変更すればよいということになります。