第1章
はじめてのデザインパターン  

第2章
逆引きカタログ ロジック編

第3章
逆引きカタログ J2EE編

第4章
逆引きカタログ その他

第5章
デザインパターン適用の勘所

第3章 逆引きカタログ J2EE編

Authors:Agata Toshitaka

Template Method(テンプレートメソッド)

 
イントロダクション
パターン解説
具体的な例
まとめ
 

イントロダクション

 プログラミングをしていると、「処理の流れは同じなんだけど、一部の処理が違う」という場面によく出会います。
 そんなとき「コピペして、一部を書き換える」というコピペマシーンになってしまう誘惑に駆られますが、コピペマシーンだと後々の変更に対処できませんし、精神衛生上もよくありません。 そんなときこそデザインパターンの出番です。
 テンンプレートメソッドパターンは、そのような「似たような流れの処理」をスーパークラスで共通化し、「固有の処理」をサブクラスにまかせることで、「処理のテンプレート」を作成するパターンです。
TOP

パターン解説

 テンプレートメソッドの例としてサーブレットを取り上げます。
 サーブレットを素直に作っていくと、全てのサーブレットに「前処理〜権限チェック〜メイン処理」のような定型的な処理の流れが出現してきます(図1の左側)。
図1 テンプレートメソッドパターン適用前/適用後
 図の例では、ServletAとServletBの「処理の流れの枠組み」は共通です。 しかし権限チェックや業務処理など「画面ごとの固有の処理」の中身は異なります。
 テンプレートメソッドではこの「処理の流れの枠組み」を共通化するために、すべてのサーブレットの共通スーパークラスを用意します(図1の右側)。 そして「共通的な部分である処理の流れの枠組み」をスーパークラスに配置します。 「固有の処理である個々の権限チェックや業務処理」は抽象メソッドとして定義し、スーパークラスでは実装しません。 具体的な個々の処理はサブクラスに実装まかせます。
 これにより、すべての処理の流れの枠組みを共通化することができ、またサブクラスでは個々の処理のみ実装すればよいという状態になります。
 イメージとしては、スーパークラスでは実装していない「穴」がいくつかあって、サブクラスではその「穴」 を埋めるだけで実装が完成するという感じです(図2)。
図2 テンプレートメソッドパターンのイメージ
MEMO
抽象メソッドって何?
 抽象メソッドは頭にabstractを付けたメソッドで、メソッドの宣言のみで実装がないのが特徴です。
 抽象メソッドには宣言のみで実装はありませんが、サブクラスで実装されることになるので、実装がない状態でもスーパークラスから呼び出すことができます。
TOP

具体的な例

 それではより具体的な例を見てみましょう(図3〜4)。
図3 サンプルのクラス図
図4 サンプルのシーケンス図
 このサンプルでは管理者のみが利用できる「管理者メニュー」と、一般ユーザが見れる「一般ユーザメニュー」の2つのメニューがあります。
 それぞれのメニュー表示用のサーブレットでは、ユーザの権限チェックを行い、画面を表示するかどうかを決定しています。 権限チェックで画面が利用できないのであれば、エラー画面を表示します。

◎AbstractServlet(リスト1

 AbstractServletは全ての業務サーブレットのスーパークラスとなるサーブレットです。
 doPost()、doGet()メソッドからexecute()メソッドを呼び出すことで、ブラウザからのリクエストをexecuteメソッドで処理しています。
 execute()メソッドでは以下の処理を行っています。
@doSetup()メソッド(前処理)の呼び出し
AdoAuth()メソッド(権限チェック)の呼び出し
BdoExecute()メソッド(業務処理)の呼び出し
 このうちdoSetup()メソッドではすべてのサーブレットでログを取りたいだけですので、スーパークラスであるAbstractServletに直接実装しています。
 doAuth()、doExecute()メソッドは個々のサーブレットで処理の内容が異なるため、抽象メソッドとして定義しています。
 AbstractServletではサブクラスで実装される、個々の処理の具体的な内容は知ることはありません。
MEMO
doXXX()メソッドってどういう意味?
 慣習上、サブクラスで実装することが期待されるメソッドは、頭に「do」が付くメドッソになることが多いです。

◎AdminMenuServlet(リスト2)、UserMenuServlet(リスト3

 AdminMenuServlet、UserMenuServletは管理者と一般ユーザのメニュー画面を表示するサーブレットで、AbstractServletのサブクラスです。
 スーパークラスの抽象メソッドdoAuth()、doExecute()を実装しています。
 管理者と一般ユーザでは使用できる画面が異なるため、doAuth()の内容が異っています。
 このようにサブクラスごとに異なる部分のみ実装すればよい、というのがテンプレートメソッドのうれしいところです。
リスト1 AbstractServlet.java

/**
 * サーブレットの抽象クラスです。
 * すべての業務処理はこのサーブレットを継承して作成します。
 */
abstract public class AbstractServlet extends HttpServlet {
	private static final Log log
				= LogFactory.getLog(AbstractServlet.class);

	/**
	* executeに処理を渡します。
	*/
	protected void doPost(HttpServletRequest req, HttpServletResponse res)
				throws ServletException, IOException {
		execute(req, res);
	}

	/**
	* executeに処理を渡します。
	*/
	protected void doGet(HttpServletRequest req, HttpServletResponse res)
				throws ServletException, IOException {
		execute(req, res);
	}

	/**
	* サーブレットの共通的な一連の処理を実行します。
	*/
	protected void execute(HttpServletRequest req, HttpServletResponse res)
				throws ServletException, IOException {

		// @前処理
		doSetup(req, res);

		// A権限チェック
		boolean auth = doAuth(req, res);
		if (!auth) {
			req.getRequestDispatcher("/error.jsp")
						.forward(req, res);
		}

		// B業務処理
		doExecute(req, res);

	}

	/**
	* 前処理を実行するメソッドです。
	* 必要に応じて下位クラスでオーバーライドしてください。
	*/
	protected void doSetup(HttpServletRequest req, HttpServletResponse res)
				throws ServletException, IOException {
		log.debug("call" + getClass.getName() + "#doSetup");
	}

	/**
	* 権限チェックを行うメソッドです。
	* 下位クラスで実装してください。
	* falseが返されると権限なしとみなし、エラー画面へ遷移します。
	*/
	abstract protected boolean doAuth(
			HttpServletRequest req, HttpServletResponse res)
				throws ServletException, IOException;

	/**
	* 業務処理を実行するメソッドです。
	* 下位クラスで実装してください。
	*/
	abstract protected boolean doExecute(
			HttpServletRequest req, HttpServletResponse res)
				throws ServletException, IOException;
}

リスト2 AdminMenuServlet.java

/**
 * 管理者メニューを表示するサーブレットです。
 */
public class AdminMenuServlet extends AbstractServlet {

	/* 権限チェックです。 */
	protected boolean doAuth(
			HttpServletRequest req, HttpServletResponse res)
				throws ServletException, IOException {
		// 未ログイン、管理者以外は使用不可
		User user = (User) req.getSession().getAttribute("user");
		if (user == null || !user.isAdmin()) {
			return false;
		} else {
			return true;
		}
	}

	/* 業務処理です。 */
	protected void doExecute(
			HttpServletRequest req, HttpServletResponse res)
				throws ServletException, IOException {
		System.out.println("業務処理実行");
		req.getRequestDispatcher("AdminMenu.jsp").forward(req, res);
	}

}
TOP

まとめ

◎サブクラスの処理はスーパークラスから呼び出される

 先ほどの例ではサブクラスで実装されたdoAuth()、doExecute()メソッドは、スーパークラスのexecute()メソッドから呼び出されました。 逆の表現を使うと、スーパークラスはサブクラスに対して、「doAuth()、doExecute()メソッドの順番で呼び出すから、よろしく実装してね!」とお願いしているようなものです。
 このような、スーパー・サブの連携を意識すると、テンプレートメソッドパターンの理解が早まることでしょう。

◎テンプレートメソッドはフレームワークの入り口です

 テンプレートメソッドパターンはフレームワークで多用されています。
 フレームワークを使うと個々の開発者はそれぞれの機能での「固有の処理」をちょっとだけ実装すればいいので、コード量が大幅に削減されます。 これはフレームワークが「共通的な処理の枠組み」を担当しているからです。
 これはまさにテンプレートメソッドの目的そのものですね。

◎ほとんど同じ処理は抽象メソッドにしない

 先ほどの例ではdoSetup()メソッドは抽象メソッドとしていません。 これはサブクラスでわざわざdoSetup()メソッドを実装する手間を省くためです。
 もちろん、doSetup()メソッドの内容をサブクラス拡張したいときは、doSetup()メソッドをオーバライドすれば対応できます。
 手間と柔軟性のトレードオフを考えると、doSetup()メソッドのように標準的な動作をする処理をスーパークラスで定義するのは「あり」でしょう。

◎他にもこんなときに使える

 テンプレートメソッドパターンは色々な所で使うことができます。「似たような処理、似たような処理の流れ」 が複数のクラスで出てきたときは、このパターンが使えないか検討してみてください。
 たとえばSQLによるSELECT文の発行を考えてみます。通常は「コネクションの取得〜SQLの生成〜SQLの発行〜データ 取得〜コネクションのクローズ」といった一連の処理になるはずです。「固有の処理」となる部分は「SQLの生成」 「データ取得」あたりですね。その他は共通処理になります。共通処理はスーパークラスに配置し「SQLの生成」 「データ取得」のみをサブクラスで実装すれば、驚くほど少ないコードでSQLの発行ができるようになるはずです。
TOP