第3章 逆引きカタログ J2EE編
Authors:Agata Toshitaka
「MVC」という言葉をご存知ですか?
MVCとは「Model(モデル)-View(ビュー)-Controller(コントローラ)」の頭文字をとった言葉で、1980年代にSmalltalkという言語で確立されたアプリケーションのアーキテクチャパターンです。
モデルはビジネスロジック(業務処理)、ビューは表示処理、コントローラはモデルとビューの操作を担当します。
J2EEではMVCをお手本に「MVC2」というアーキテクチャを採用しています。
サーブレット/JSPベースのWebアプリケーションの場合、一般的にモデルはJavaBeans (あるいはEJB)、ビューはJSP、Web層のコントローラがサーブレット、ビジネス層のコントローラが先ほどのファサードとして実装されます。
本節ではMVCの「V」の部分、つまりJSPに注目してみます。
JSPはビューですので表示処理のみを行い、ビューとしての役割を果たすべきです。
しかし、私たち開発者がちょっとでも油断をすると、すぐにロジックを含んだスパゲッティなJSPができあがってしまいます。
これは「表示系のロジック」をビューからうまく切り出すことで、改善できます。
ここでは、JSPのリファクタリングを通して、ビューヘルパというパターンを解説していきます。
ビューヘルパパターンの目的はJSPから「<% ... %>」というスクリプトレットをなくすことです。
簡単に言うと、表示を助ける別のJavaクラスにスクリプトレットを移動させれば完成します(図9)。
図9 ビューヘルパ適用前/適用後
具体的には、次の2つの方法で実装されることが多いです。
- @JavaBeans戦略
-
普通のJavaBeansとしてビューヘルパを実装します。乱暴にいうと普通のクラスに移動させるだけです。
次の「具体的な例」で詳述します。
- Aカスタムタグ戦略
-
カスタムタグもビューヘルパを実現する1つの方法です。しかし、カスタムタグ戦略はJavaBeansと比べて作業量が多く、維持管理も複雑になります。
筆者のおすすめは理解しやすくテストや保守も容易な@のJavaBeans戦略です。
それではより具体的な例を見てみましょう。
サンプルの例は書籍データの検索処理です。
今回も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)。
・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;| @ |
public BookHelper(Book book) {
this.book = book;
}
|
| A |
public Book getBook() {
return book;
}
|
| B |
public boolean isFreeDelivery() {
return (getBook().getPrice() >= FREE_DELIVERY_PRICE);
}
|
| C |
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>
◎BookクラスにgetShortTitle()、isFreeDelivery()メソッドをつけたらだめなの?
これはケースバイケースです。
今回のgetShortTitle()メソッドによる10文字制限がWeb画面特有の処理ならば、ビューヘルパを使ったほうがよいでしょう。
逆にisFreeDelivery()メソッドがアプリケーション全体で利用される場合はBookクラスに実装したほうがよいでしょう。
ビューヘルパはあくまでも「ビュー」のヘルパであることを意識し、表示を助ける処理を持たせましょう。
◎ビューヘルパクラスはユーティリティクラスで実装しちゃだめ?
筆者はありだと考えています。
その場合、ユーティリティクラス(第4章参照)の命名規則や、分割単位を工夫したほうがよいでしょう。
重要なのはあくまでもJSPからコードを取り除くことです。
◎データの整形処理はどこで行うの?
画面に表示することを目的としたデータの整形処理は、専用のクラスを用意したほうがよい場合が多いです。
もしも、データベースから取得した時点のDAOクラスなどで、ある画面に合わせてデータを整形すると、そのDAOクラスはその画面専用のクラスになってしまいます。
このことが絶対に悪いわけではありませんが、アプリケーションの持つ性質(ユーザインターフェイスはWebのみなのか? 再利用性を重視するか? など)に合わせて、「データ整形をどこでおこなうか?」ということを意識的に決定することをお勧めします。
◎カスタムタグ戦略についての補足
今回はカスタムタグ戦略はご紹介しませんでしたが、JSP 2.0からはSimpleTagSupportとタグファイルという、カスタムタグを簡単に作成する仕組みが用意されています。
これらの機能を使用すると、カスタムタグ戦略の導入がずいぶん楽になります。
また、サンプルではStrutsとJSTL(Java Standard Tag Library)のタグを使用しています。
これらは立派なカスタムタグ戦略です。
カスタムタグがすでに存在するのなら有効に利用しましょう。
特にJSTLはその名のとおり標準タグライブラリですので、積極的に活用することをおすすめします。