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

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

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

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

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

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

Authors:Agata Toshitaka

ViewHelper (ビューヘルパ)

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

イントロダクション

 「MVC」という言葉をご存知ですか? MVCとは「Model(モデル)-View(ビュー)-Controller(コントローラ)」の頭文字をとった言葉で、1980年代にSmalltalkという言語で確立されたアプリケーションのアーキテクチャパターンです。 モデルはビジネスロジック(業務処理)、ビューは表示処理、コントローラはモデルとビューの操作を担当します。
 J2EEではMVCをお手本に「MVC2」というアーキテクチャを採用しています。 サーブレット/JSPベースのWebアプリケーションの場合、一般的にモデルはJavaBeans (あるいはEJB)、ビューはJSP、Web層のコントローラがサーブレット、ビジネス層のコントローラが先ほどのファサードとして実装されます。
 本節ではMVCの「V」の部分、つまりJSPに注目してみます。
 JSPはビューですので表示処理のみを行い、ビューとしての役割を果たすべきです。 しかし、私たち開発者がちょっとでも油断をすると、すぐにロジックを含んだスパゲッティなJSPができあがってしまいます。 これは「表示系のロジック」をビューからうまく切り出すことで、改善できます。
 ここでは、JSPのリファクタリングを通して、ビューヘルパというパターンを解説していきます。
TOP

パターン解説

 ビューヘルパパターンの目的はJSPから「<% ... %>」というスクリプトレットをなくすことです。
 簡単に言うと、表示を助ける別のJavaクラスにスクリプトレットを移動させれば完成します(図9)。
図9 ビューヘルパ適用前/適用後


具体的には、次の2つの方法で実装されることが多いです。
@JavaBeans戦略
 普通のJavaBeansとしてビューヘルパを実装します。乱暴にいうと普通のクラスに移動させるだけです。 次の「具体的な例」で詳述します。
Aカスタムタグ戦略
 カスタムタグもビューヘルパを実現する1つの方法です。しかし、カスタムタグ戦略はJavaBeansと比べて作業量が多く、維持管理も複雑になります。
 筆者のおすすめは理解しやすくテストや保守も容易な@のJavaBeans戦略です。
TOP

具体的な例

 それではより具体的な例を見てみましょう。
 サンプルの例は書籍データの検索処理です。 今回もStrutsを題材にしていますが、基本的な考え方はサーブレットや他のフレームワークにも応用が利くはずです。
 「スパゲッティJSP」をビューヘルパパターンによりリファクタリングしていく過程をご覧あれ。

◎第1段階:スパゲッティーJSP

 まずはビューヘルパパターン適用前のスパゲッティJSP(リスト12)を見てみましょう。
リスト12 FindBookResultBefore.jsp

<html>
<head>
<title>書籍検索結果画面</title>
</head>
<body>
<html:errors/>
書籍ID:<c:out value="${book.id}" /><br>
@
<%
	//短いタイトルを作成
	Book book = (Book)request.getAttribute("book");
	String title = book.getTitle();
	if (title.length() <= 10) {
		return title;
	} else {
		return title.substring(0, 10) + "...";
	}
%>
タイトル:<%=shortTitle%><br> 価格:<c:out value="${book.price}"/><br> <c:choose> <c:when test="${book.price >= 1000}"> …A 配送料は無料です。 </ c:when> <c:otherwise> 配送料は有料です。 </c:otherwise> </c:choose> </body> </html>
このJSPでは検索結果の書籍データ(Bookクラス.リスト13)を表示しています。 書籍データの表示に関しては次のようなルールを取り決めています。
@書籍名が10文字を超える場合は、10文字まで表示して後は「...」をうしろにつける(例:Javaセンスアッププログラミング→Javaセンスアップ...)
A価格が1000円を超えるものは「配送料は無料です。」と表示する
 このルールを守って素直にJSPを作ると、リスト12-@Aのように、ルール(ビジネスロジック)がJSPに埋め込まれてしまいます。
 他の画面で同じルールを適用する場合、たぶんこのJSPから「コピペ」という手段がとられる可能性が高いです。
 特にAなどは単純な条件分岐なので甘く見てしまいそうですが、この条件分岐のルールがまったく変更になったときに、アプリケーション全体にこのコードが散りばめられていたとしたら、変更には多くの作業とテストが必要になることでしょう。
 ちなみに「スパゲッティJSP」と銘打っていますが、このサンプルは症状がかなり軽いほうだと思います。 もっとスパゲッティで保守不可能なJSPを近くで見たことありませんか??
リスト13 Book.java

/**
 * 書籍データです。
 */
public class Book {

	/**
	* 検索処理を行います。
	*/
	private int id;
	private String title;
	private int price;


	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}

	public int getPrice() {
		return price;
	}
	public void setPrice(int price) {
		this.price = price;
	}

	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
}

◎第2段階:ViewHelperの適用

 それではビューヘルパパターン適用後の状態を見てみましょう(図10〜11)。
図10 ビューヘルパパターン適用時のクラス図
図11 ビューヘルパパターン適用時のシーケンス図
・FindBookAction(リスト14
 書籍検索アクションクラスです。 リスト14-@のように、検索結果の書籍データ(Bookクラス.リスト13)をビューヘルパクラスであるBookHelperクラスで包み込んでJSPに引き渡しています。
リスト14 FindBookAction.java

/**
 * 書籍データ検索アクションです。
 */
public class FindBookAction {
	/**
	 * 検索処理を行います。
	 */
	 public ActinForward execute(ActionMapping mapping, ActionForm form,
 			HttpServletRequest req, HttpServletResponse res)
	 					throws Exception {

		FindBookForm findBookForm = (FindBookForm) form;

		BookService service = new BookServiceImpl();
		Book book = service.findBook(findBookForm.getId());
		if (book == null) {
		  return mapping.findForward("bookNotFound");
		} else {
		  req.setAttribute("bookHelper", new BookHelper(book)); …@
		  return mapping.findForward("success");
		}
	}
}
・BookHelper(リスト15
 ビューヘルパクラスです。JSPにあったロジックはすぺてここに移動しています。
 このクラスの特徴は、リスト15-@のようにコンストラクタで操作対象のBookオブジェクトを受け取り、条件分岐処理(リスト15-B)や装飾処理(リスト15-C)を行っている点です。
 また、元のBookオブジェクトを取り出せるようにゲッタメソッドも用意 しています(リスト15-A)。
リスト15 BookHelper.java

/**
 * 書籍データの表示用ヘルパクラスです。
 */
public class BookHelper {

	/** 無料配送になる最低価格です。 */
	private static final int FREE_DELIVERY_PRICE = 1000;

	/** 短いタイトルの上限です。 */
	private static final int SHORT_TITLE_LENGTH = 10;

	/** 書籍データです。 */
	private final Book book;
@
	/**
	 * コンストラクタです。
	 * @param book書籍データ
	 */
	public BookHelper(Book book) {
		this.book = book;
	}
A
	/**
	 * 書籍データを取得します。
	 * @return Book
	 */
	public Book getBook() {
		return book;
	}
B
	/**
	 * 配送無料かどうかを判定します。
	 * @return trueなら配送無料
	 */
	public boolean isFreeDelivery() {
		// (筆者注)
		// ビューヘルパの説明として、
		// 配送無料かどうかのロジックをここで実装しているが、
		// このロジック他の場所でも使用する可能性が高いため、
		// 別のクラスで実装し、それを呼び出す形にしたほうが望ましい。
		return (getBook().getPrice() >= FREE_DELIVERY_PRICE);
	}
C
	/**
	 * 10文字以下の短いタイトル名を取得します。
	 * タイトルが10文字以上の場合、
	 * 10文字で切り取って「...」を付加したタイトルを返します。
	 * @return 10文字以下の短いタイトル名
	 */
	public String getShortTitle() {
		String title = getBook().getTitle();
		if (title.length() <= SHORT_TITLE_LENGTH) {
		  return title;
		} else {
		  return title.substring(0, SHORT_TITLE_LENGTH) + "...";
		}
	}
}
・FindBookResult.jsp(リスト16
 リファクタリング後のJSPです。スクリプトレットを一掃し、代わりにBookHelperを使用するように変更しています(リスト16-@A)。
 JSPとしての保守性も高まり、コードはすっきりと見やすくなっています。
リスト16 FindBookResult.jsp

<c:set var="book" value="${bookHelper.book}"/>

<html:html>
<head>
<title>書籍検索結果画面</title>
</head>
<body>
<html:errors/>
書籍ID:<c:out value="${book.id}"/><br>
タイトル:<c:out value="${bookHelper.shortTitle}"/><br> …@
価格:<c:out value="${book.price}"/><br>
<c:choose>
	<c:when test="${bookHelper.freeDelivery}"> …A
	配送料は無料です。
	</c:when>
	<c:otherwise>
	配送料は有料です。
	</c:therwise>
</c:choose>
</body>
</html:html>
TOP

まとめ

◎BookクラスにgetShortTitle()、isFreeDelivery()メソッドをつけたらだめなの?

 これはケースバイケースです。
 今回のgetShortTitle()メソッドによる10文字制限がWeb画面特有の処理ならば、ビューヘルパを使ったほうがよいでしょう。 逆にisFreeDelivery()メソッドがアプリケーション全体で利用される場合はBookクラスに実装したほうがよいでしょう。
 ビューヘルパはあくまでも「ビュー」のヘルパであることを意識し、表示を助ける処理を持たせましょう。

◎ビューヘルパクラスはユーティリティクラスで実装しちゃだめ?

 筆者はありだと考えています。
 その場合、ユーティリティクラス(第4章参照)の命名規則や、分割単位を工夫したほうがよいでしょう。 重要なのはあくまでもJSPからコードを取り除くことです。

◎データの整形処理はどこで行うの?

 画面に表示することを目的としたデータの整形処理は、専用のクラスを用意したほうがよい場合が多いです。
 もしも、データベースから取得した時点のDAOクラスなどで、ある画面に合わせてデータを整形すると、そのDAOクラスはその画面専用のクラスになってしまいます。 このことが絶対に悪いわけではありませんが、アプリケーションの持つ性質(ユーザインターフェイスはWebのみなのか? 再利用性を重視するか? など)に合わせて、「データ整形をどこでおこなうか?」ということを意識的に決定することをお勧めします。

◎カスタムタグ戦略についての補足

 今回はカスタムタグ戦略はご紹介しませんでしたが、JSP 2.0からはSimpleTagSupportとタグファイルという、カスタムタグを簡単に作成する仕組みが用意されています。 これらの機能を使用すると、カスタムタグ戦略の導入がずいぶん楽になります。
 また、サンプルではStrutsとJSTL(Java Standard Tag Library)のタグを使用しています。 これらは立派なカスタムタグ戦略です。
 カスタムタグがすでに存在するのなら有効に利用しましょう。 特にJSTLはその名のとおり標準タグライブラリですので、積極的に活用することをおすすめします。
TOP