第3章 逆引きカタログ J2EE編
Authors:Agata Toshitaka
プログラミングをしていると、「処理の流れは同じなんだけど、一部の処理が違う」という場面によく出会います。
そんなとき「コピペして、一部を書き換える」というコピペマシーンになってしまう誘惑に駆られますが、コピペマシーンだと後々の変更に対処できませんし、精神衛生上もよくありません。
そんなときこそデザインパターンの出番です。
テンンプレートメソッドパターンは、そのような「似たような流れの処理」をスーパークラスで共通化し、「固有の処理」をサブクラスにまかせることで、「処理のテンプレート」を作成するパターンです。
テンプレートメソッドの例としてサーブレットを取り上げます。
サーブレットを素直に作っていくと、全てのサーブレットに「前処理〜権限チェック〜メイン処理」のような定型的な処理の流れが出現してきます(図1の左側)。
図の例では、ServletAとServletBの「処理の流れの枠組み」は共通です。
しかし権限チェックや業務処理など「画面ごとの固有の処理」の中身は異なります。
テンプレートメソッドではこの「処理の流れの枠組み」を共通化するために、すべてのサーブレットの共通スーパークラスを用意します(図1の右側)。
そして「共通的な部分である処理の流れの枠組み」をスーパークラスに配置します。
「固有の処理である個々の権限チェックや業務処理」は抽象メソッドとして定義し、スーパークラスでは実装しません。
具体的な個々の処理はサブクラスに実装まかせます。
これにより、すべての処理の流れの枠組みを共通化することができ、またサブクラスでは個々の処理のみ実装すればよいという状態になります。
イメージとしては、スーパークラスでは実装していない「穴」がいくつかあって、サブクラスではその「穴」
を埋めるだけで実装が完成するという感じです(図2)。
| 抽象メソッドって何? |
抽象メソッドは頭にabstractを付けたメソッドで、メソッドの宣言のみで実装がないのが特徴です。
抽象メソッドには宣言のみで実装はありませんが、サブクラスで実装されることになるので、実装がない状態でもスーパークラスから呼び出すことができます。
|
それではより具体的な例を見てみましょう(図3〜4)。
このサンプルでは管理者のみが利用できる「管理者メニュー」と、一般ユーザが見れる「一般ユーザメニュー」の2つのメニューがあります。
それぞれのメニュー表示用のサーブレットでは、ユーザの権限チェックを行い、画面を表示するかどうかを決定しています。
権限チェックで画面が利用できないのであれば、エラー画面を表示します。
◎AbstractServlet(リスト1)
AbstractServletは全ての業務サーブレットのスーパークラスとなるサーブレットです。
doPost()、doGet()メソッドからexecute()メソッドを呼び出すことで、ブラウザからのリクエストをexecuteメソッドで処理しています。
execute()メソッドでは以下の処理を行っています。
- @doSetup()メソッド(前処理)の呼び出し
- AdoAuth()メソッド(権限チェック)の呼び出し
- BdoExecute()メソッド(業務処理)の呼び出し
このうちdoSetup()メソッドではすべてのサーブレットでログを取りたいだけですので、スーパークラスであるAbstractServletに直接実装しています。
doAuth()、doExecute()メソッドは個々のサーブレットで処理の内容が異なるため、抽象メソッドとして定義しています。
AbstractServletではサブクラスで実装される、個々の処理の具体的な内容は知ることはありません。
| 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);
protected void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
execute(req, res);
}
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);
boolean auth = doAuth(req, res);
if (!auth) {
req.getRequestDispatcher("/error.jsp")
.forward(req, res);
}
doExecute(req, res);
}
protected void doSetup(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
log.debug("call" + getClass.getName() + "#doSetup");
}
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);
}
}
◎サブクラスの処理はスーパークラスから呼び出される
先ほどの例ではサブクラスで実装されたdoAuth()、doExecute()メソッドは、スーパークラスのexecute()メソッドから呼び出されました。
逆の表現を使うと、スーパークラスはサブクラスに対して、「doAuth()、doExecute()メソッドの順番で呼び出すから、よろしく実装してね!」とお願いしているようなものです。
このような、スーパー・サブの連携を意識すると、テンプレートメソッドパターンの理解が早まることでしょう。
◎テンプレートメソッドはフレームワークの入り口です
テンプレートメソッドパターンはフレームワークで多用されています。
フレームワークを使うと個々の開発者はそれぞれの機能での「固有の処理」をちょっとだけ実装すればいいので、コード量が大幅に削減されます。
これはフレームワークが「共通的な処理の枠組み」を担当しているからです。
これはまさにテンプレートメソッドの目的そのものですね。
◎ほとんど同じ処理は抽象メソッドにしない
先ほどの例ではdoSetup()メソッドは抽象メソッドとしていません。
これはサブクラスでわざわざdoSetup()メソッドを実装する手間を省くためです。
もちろん、doSetup()メソッドの内容をサブクラス拡張したいときは、doSetup()メソッドをオーバライドすれば対応できます。
手間と柔軟性のトレードオフを考えると、doSetup()メソッドのように標準的な動作をする処理をスーパークラスで定義するのは「あり」でしょう。
◎他にもこんなときに使える
テンプレートメソッドパターンは色々な所で使うことができます。「似たような処理、似たような処理の流れ」
が複数のクラスで出てきたときは、このパターンが使えないか検討してみてください。
たとえばSQLによるSELECT文の発行を考えてみます。通常は「コネクションの取得〜SQLの生成〜SQLの発行〜データ
取得〜コネクションのクローズ」といった一連の処理になるはずです。「固有の処理」となる部分は「SQLの生成」
「データ取得」あたりですね。その他は共通処理になります。共通処理はスーパークラスに配置し「SQLの生成」
「データ取得」のみをサブクラスで実装すれば、驚くほど少ないコードでSQLの発行ができるようになるはずです。