デザインパターン TemplateMethodについて勉強した

使用言語はJavaです。

Template Methodパターンとは

処理の流れは決まっているけど、具体的な処理の内容は決まっていないような場合に使用されるパターンです。

Template Methodパターンの例

働く人をイメージしてみます。
業種はいろいろありますが、それらをまとめて抽象的なものとしてWorker(働く人)クラスとして定義します。

public abstract class Worker {

	public void work() {
		System.out.println("出勤");
		am();
		System.out.println("昼ごはん");
		pm();
		System.out.println("退勤");
	}

	public abstract void am();

	public abstract void pm();
}

amメソッドはam(午前)の仕事内容を定義するための抽象メソッドとします。
なぜ抽象かというと、仕事内容は業種によって異なるからです。
プログラマならデスクワークでしょうし、トラックドライバーなら運転をします。
それらはWorkerクラスを継承した各サブクラスに実装させて、このクラスではメソッドが存在することだけわかればいいことにします。

pmメソッドはpm(午後)の仕事内容を定義するための抽象メソッドです。
役割はamメソッドと同じです。

workメソッドは抽象メソッドではありません。
このメソッドを呼び出すことで、1日の仕事があるべき順に実行されていくようになっています。
amメソッド、pmメソッドが組み込まれていますが、これらはサブクラスで処理を実装されることで、各業種として機能するようになります。


Programmerクラス

public class Programmer extends Worker {

	@Override
	public void am() {
		System.out.println("打ち合わせ");
	}

	@Override
	public void pm() {
		System.out.println("コーディング");
	}
}

Workerクラスが継承されています。
これによりam,pmメソッドの実装が強制されるので、実装忘れをすることはありません。
amメソッドでは"打ち合わせ"を行なっています。
pmメソッドでは"コーディング"を行なっています。


Driverクラス

public class Driver extends Worker {

	@Override
	public void am() {
		System.out.println("運転");
	}

	@Override
	public void pm() {
		System.out.println("運転");
	}

	public void loading() {
		System.out.println("積み込み");
	}
}

こちらもWorkerクラスを継承しています。
Programmerクラスとは各メソッドの実装内容が変わっており、am,pmメソッドとも"運転"をするようになっています。
またrefuelingメソッドが新たに実装されています。refuelingは給油という意味です。


テスト用クラス

ublic class Main {

	public Main() {
		System.out.println("--------");
		Worker driver = new Driver();
		driver.work();
		//driver.loading();

		System.out.println("--------");
		Worker programmer = new Programmer();
		programmer.work();
	}

	public static void main(String[] args) {
		new Main();
	}
}

Worker型のdriver変数にはDriverクラスのインスタンスが代入されます。
ここでDriver型ではなくWorker型の変数としているのはクラスを部品として扱いやすいようにしているためです。
仮にこのnew Driver()をnewProgrammer()や新たに作ったnew TaxiDriver()に変更する場合もコンストラクタ呼び出しの1箇所だけ変更すればいいことになります。

もしも変数の型がDriverであり、コードのどこかにloadingメソッドが使用されていた場合を考えてみてください。
その上でdriver変数をTaxiDriver型に変更したいとなった場合、loadingメソッドを削除して、ちゃんと動くかを確認する必要が生じます。

これがWorker型変数であるだけでloadingメソッドの使用を禁止し、結果としてloadingメソッドの削除や変更の確認をしなくても良くなるわけです。

実行

実行してみましょう。

--------
出勤
運転
昼ごはん
運転
退勤
--------
出勤
打ち合わせ
昼ごはん
コーディング
退勤

ProgrammerクラスやDriverクラスにはam,pmメソッドを実装するだけで1日の仕事の流れが作成できました。
TaxiDriverクラスやSportsmanクラスを新たに作りたくなった場合も簡単に対応できるようになります。

また、全ての業種で出勤の後に「朝礼」を追加したくなった場合にはWorkerクラスのworkメソッドをちょっと変更するだけで済みます。

	public void work() {
		System.out.println("出勤");
		System.out.println("朝礼");    //追加
		am();
		System.out.println("昼ごはん");
		pm();
		System.out.println("退勤");
	}

まとめ

冒頭で「処理の流れは決まっているけど、具体的な処理の内容は決まっていないような場合」と言いましたが、「処理の流れ」はworkメソッド、「具体的な処理の内容」はam,pm抽象メソッドのことです。

Template Methodパターンを使用しない場合、ProgrammerクラスとDriverクラスは同じworkメソッドを持つことになります。
クラスが2つであればまだいいですが、10や20も業種別にクラスがあると、workメソッド内のたった1行を変更しようとなった時にも大変な作業になってしまいます。
また複数の業種がランダムに並べられた配列を一つ一つworkメソッドを呼ぶことになった場合でも、各業種ごとにコードを書かなければならなくなります。

for(Worker worker : list){
	worker.work();
}

また、Template Methodパターンの欠点として、具体的な処理の内容を追加するのが難しい点があります。

public abstract class Worker {

	public void work() {
		System.out.println("出勤");
		System.out.println("朝礼");
		am();
		System.out.println("昼ごはん");
		pm();
		System.out.println("退勤");
		overtime();                //追加
	}

	public abstract void am();

	public abstract void pm();
	
	public abstract void overtime();        //追加
}

新たにovertime(残業)抽象メソッドが追加されました。
この場合はWorkerクラスを継承した全てのサブクラスに変更が発生するため、サブクラス数によっては大変な変更になってしまいます。