Wicketで始めるオブジェクト指向ウェブ開発

第2回コンポーネントとモデル

今回は、Wicketのオブジェクト指向によるWeb開発を支える基盤である「コンポーネント」「モデル」の関係について紹介します。

ページはコンポーネントの固まり

Wicketのウェブページは「コンポーネント」の固まりです。コンポーネントとはユーザへの表示とユーザからの入力を受け付けるオブジェクトです。すべてのコンポーネントはComponentのサブクラスとして作られています。ウェブページ自身を表すPageクラスすらも、Componentのサブクラスです。

Wicketのページは、Pageという一番親となるコンポーネントの上に、さまざまなコンポーネントが載せられたような構造になっています。この構造は、デスクトップ・アプリケーションにおいて、ボタンやフィールドなどのコンポーネントがウインドウという親コンポーネントに載せられているのと同じ考え方です。

HTML要素=コンポーネント

Wicketでは、ほぼすべてのHTMLは対応するWicketコンポーネントによって制御できます。<input type="text">であれば、WicketのTextFieldコンポーネントを要素に適用することで、タグの表示や入出力をプログラムからコントロールできます。

<form>はFormコンポーネントによって、<input type="button">や<button>、<a>などはLinkコンポーネントによって、ただのタグを「オブジェクト」にすることができます。コンポーネントの属性を変えるということは、ブラウザに出力されるタグをコントロールすることと同じです。Wicketは、HTMLタグにコンポーネントを適用することによって、静的なHTMLを動的なオブジェクトとして扱うのです。

コンポーネントはタグの表示をコントロールする

コンポーネントはHTML要素を制御するものですから、コンポーネントを操作することで、HTMLの生成をコントロールすることができます。逆に言えば、HTMLの表示を制御したいのであれば、その場所にコンポーネントを適用すればよいのです。

非常にシンプルな例が、前回作成したQuickstartアプリケーションで使われています。Labelコンポーネントです。

Labelコンポーネントによる表示のコントロール

HTML要素にコンポーネントを適用する

第1回で作成したquickstartアプリケーションに「HomePage.html」「HomePage.java」の2ファイルがありました。HomePage.htmlを制御しているプログラムが、HomePage.javaです。先にHTMLファイルを見てみましょう。次のようなシンプルなものです。

リスト1 HomePage.htmlの内容
<html xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd" >
    <head>  
        <title>Wicket Quickstart Archetype Homepage</title>
    </head>
    <body>
        <strong>Wicket Quickstart Archetype Homepage</strong>
        <br/><br/>
        <span wicket:id="message">message will be here</span>
    </body>
</html>

強調表示の部分にある「message will be here」という文字は、実際にブラウザで表示したときには「If you see this message wicket is properly configured and running」に変わっていました[1]⁠。プログラムから、この部分が変更されたわけです。

WicketのコンポーネントはHTML要素を制御すると説明しました。今回は、どの要素が制御されているのでしょうか。

それは、メッセージが含まれている<span>タグです。コンポーネントによって<span>タグを制御して、表示を変えたのです。

強調している行には「wicket:id="message"」という属性が付けられています。この「wicket:id」属性が、要素をコンポーネントによってプログラム制御する、というWicketに対する指定になります。

では、要素にコンポーネントを適用するコードを見てみましょう。

リスト2 HomePageクラスのコンストラクタ
public HomePage(final PageParameters parameters) {

  // Add the simplest type of label
  add(new Label("message", "If you see this message wicket is properly configured and running"));

// TODO Add your page's components here
}

Wicketでは、ページの構築はWebPageのサブクラスを作成することで行います。HomePageクラスもWebPageクラスのサブクラスです。HomePageクラスのコンストラクタ内でHTML要素へのコンポーネント適用が行われています。

Wicketのコンポーネントは、第1引数に常にIDを受け取ります。このIDが、HTML上に指定したwicket:idと対応します。ここでは第1引数に「message」を指定しています。HTMLファイル内でwicket:id="message"と指定された<span>タグに、Labelコンポーネントを適用しているのです。

Labelコンポーネントの役割は、適用したHTML要素のボディ部分をプログラムから書き換えることです。一番シンプルな方法は、Labelの第2引数に文字列を指定することです。前述のソースコードでは、第2引数に、まさしくブラウザ上で見たあの文字が指定されています。

コンポーネントにはそれぞれ役割があります。その役割に応じて引数も異なっています。各コンポーネントの使い方を押さえることが、Wicketを使いこなす早道となります。

コンポーネントはオブジェクト

Labelコンポーネントを作るたびに第2引数に渡す引数を変更すれば、HomePageを表示するたびにメッセージを変更することもできそうです。その方法はHomePageクラスでは使用可能ですが、Wicketのあらゆるページで使用可能な方法ではありません。ここからWicketのオブジェクト指向の世界に入っていきます。

オブジェクト指向言語における「オブジェクト」にはさまざまな性質がありますが、重要な機能として「自分自身の状態を自分で保持できる」という点があります。オブジェクトは、自分がどういう状態なのかを知っています。オブジェクト指向プログラミングの世界でテレビというオブジェクトのスイッチを入れれば、テレビは現在スイッチがオンであるということを自分自身の中に保持するでしょう。その情報はセッションに格納されたりはしません。

たとえば「そのHTML要素は表示されるかどうか」という状態も、コンポーネントが管理する情報のひとつです。Labelコンポーネントは自分が表示されるべきかどうかを知っています。Wicketは、タグを表示するときに、表示してよいのかどうかを各コンポーネントに問い合わせます。

コンポーネントが表示されるかどうかは、isVisible()メソッドを介して制御されます。

次のようにLabelのisVisible()メソッドを「常にfalseを返す」と再定義すれば、Labelを適用した<span>タグは二度と表示されません[2]⁠。

リスト3 Labelを非表示にする
add(new Label("message", "If you see this message wicket is properly configured and running") {
  public boolean isVisible() { return false; }
});

Labelが表示されるべきかどうかはLabel自身によってコントロールされるべきで、外部から操作することで切り替えるものではない、というのが、Wicketの基本的な考え方です。

このプログラムでは毎回falseが返却されますが、たとえば、現在時刻の「分」が偶数の時だけtrueを返す、というプログラムをここに書くことで、Labelは偶数分の時だけ表示されるようになります。Label自身が自分を表示すべきかどうかを管理するのです。

オブジェクト自身に表示を変えさせる

Wicketでは、ページやコンポーネント自身が情報を保持できます。HomePageページは単純な表示のみのページなので、毎回ページを作り直してもよいでしょう。しかし、ページへの入力が行えるようなページでは、ページの状態はユーザの入力によって次々と変化していきます。そのとき、ページやコンポーネントは毎回作られるのではなく、一度作成したページやコンポーネントが続けて使用されます。当然です。ページやコンポーネントが情報を保存しているのですから、毎回消えて再作成されても困るのです。以前の状態を持っている、同じオブジェクトが使われなければいけません。

ということは、ページやLabelのコンストラクタは一度しか呼び出されない可能性があるということです。コンストラクタで毎回Labelの第2引数を変更する、という方法では駄目なことがあるのです。

Labelが表示する内容が毎回変化するのであれば、次に表示すべきメッセージは何なのか、Label自身が知っているべきです。そうすることにより、このLabelはページとは切り離しても「毎回表示するメッセージが変わるLabel」という独立した存在になり得ます。これがオブジェクト指向の便利なところです。

そこで、Labelオブジェクトを作るときにコンストラクタで表示メッセージを指定するのではなく、Label自身が、表示されるときにメッセージを決定できる方法が良さそうです。

モデルによるコンポーネントとデータの分離

しかしそうはいっても、表示するメッセージ毎に新しいLabelサブクラスを作っていてはきりがありません。へたしたらページを作るたびに新しいLabelクラスを作る羽目になりそうです。

Wicketでは、そのために「表示を制御するコンポーネント」「データを制御するオブジェクト」とを分離しました。Labelが表示すべきデータを決定する、という機能を別のオブジェクトとすることで、データの決定方法をプラグイン可能にしたのです。

表示すべきデータなしには、表示の制御を行うことはできません。コンポーネントは、モデルと組み合わせることではじめて機能するのです。両者を別にしたことで「⁠⁠どのようなメッセージかは決まっていないが)メッセージをランダムに表示するLabel」というコンポーネントを作ることができます。このLabelは、データを提供する「モデル」を差し替えることで、どこででも使うことができます。

Labelは、モデルを与えられると、モデルを自分自身の中に保持します。データ自体ではなく、データを提供してくれるオブジェクトを保持するのです。以降、Labelは表示を行うたびに、表示すべきデータをモデルから提供してもらいます。

Labelコンポーネントの機能をより正確に表現すると、⁠モデルによって提供されたデータをHTML要素内に表示する」という機能を持ったコンポーネント、ということになります。

時刻を表示する

では実際に、Labelを使って自動的に切り替わるメッセージを表示してみます。Labelに「そのときの時刻をとってくるモデル」を渡せば、Labelは毎回モデルから時刻を取り出して表示します。

リスト4 Labelに時刻を返すモデルを渡す例
add(new Label("message", new AbstractReadOnlyModel<String>() {
  public String getObject() { 
    return new SimpleDateFormat("HH時mm分ss秒").format(new Date()); 
  }
}));

AbstractReadOnlyModelが、⁠モデル」と呼んできたものです。Wicketのモデルとは、IModelインタフェースを実装したオブジェクトです。IModelから値を取り出すにはgetObject()メソッドを使用します。逆にIModelにデータを与えるには、setObject()メソッドを使用します。

AbstractReadOnlyModelは、モデルに値を与える必要がない場合に使用します。AbstractReadOnlyModelではsetObject()は使用できません。getObject()のみが有効になっており、このメソッドをオーバーライドすることで、データを提供する読み取り専用のモデルを実現します。

今回は、getObject()が呼び出されるたびに現在時刻を文字列として返すよう、getObject()メソッドを定義しました。

mvn jetty:runコマンドでアプリケーションを起動し、http://localhost:8080/wicket-sample/top にアクセスしてください。以前はメッセージが表示されていたところに、今度は現在時刻が表示されています。

図1 現在時刻を表示するページ
図1 現在時刻を表示するページ

ブラウザの再ロードボタンを押すと、押すたびに時刻が変わります。Labelがモデルから表示データを取得することで、表示が切り替わっているのです。Labelはデータ提供者となるモデルを内部に保持することで、自分で再ロードのたびに表示内容を切り替えるのです。

プッシュからプルへ

Wicketはオブジェクト指向のWebアプリケーション・フレームワークです。ページはオブジェクトの組み合わせによって作成します。そして、オブジェクトは自分自身のことを知っています。Labelは自分自身を表示すべきかどうかを自分で管理します。Labelの表示内容は外からセットされるのではありません。WicketはLabelに「自分自身を表示しろ」と指令するだけです。あとはLabelが自分でモデルからひっぱり出し、HTMLタグを変更します。

オブジェクトは能動的なのです。必要なことは自分で行おうとします。プログラマは、それらオブジェクトの動作をプログラムすることで、アプリケーションの動きを変えるのです。

オブジェクト自身に仕事をさせる

文字列の表示・非表示の切り替えを「手続きの流れ」としてプログラムを考えた場合、次のような流れになるでしょう。

  1. 全体を管理するプログラムが状態をチェックする
  2. 表示すべき状態でないならば、そのプログラムがLabelの表示・非表示フラグを切り替える

読者のみなさんも、⁠LabelにsetVisible()のようなメソッドがあって、プログラムからsetVisible(false)を呼び出すことでLabelの表示を切り替える」といった流れのほうがなじみがある、という方も多いでしょう。

一方Wicketでは、isVisible()メソッドが呼ばれたときにLabel自身がその時々の状況、自分の状態に応じてtrue/falseを返すべきだ、という考え方をします。表示・非表示はオブジェクトの属性なのだから、オブジェクト自身が判断して結果を返せばよい、と考えるわけです。

オブジェクトの外で判断してオブジェクトの状態をセットする(プッシュする)のではなく、オブジェクト自身に自分の状態を管理させて、必要であれば外部から「表示してもよいか」と情報をひっぱる(プルする)のです。情報をひっぱったときの状況に応じて、オブジェクト自身がtrueかfalseかを判断して返します。そのため、⁠表示すべきかどうか」という判断は、オブジェクトの外側ではなく、オブジェクトの中に書くことになります。isVisible()メソッドをオーバーライドして、そこに状況を判断するプログラムを書くのです。

プッシュ型からプル型への考え方の変更は、Wicketを使いこなす上で重要なポイントです。オブジェクト指向的なプログラムが作成できるかどうかは、⁠オブジェクト自身に自分の仕事をさせる」という手法でプログラムを書けるかどうかにかかっています。

入力をコントロールする

この回では、Wicketはコンポーネントとモデルというオブジェクトを組み合わせてページを作成することを紹介しました。たったひとつのコンポーネント「Label」を使っただけではありますが、オブジェクト自身が自分の動作を決定するようにプログラムを書いていく、という感じは伝わったかと思います。

途中でさりげなく、IModelインタフェースにはgetObject()だけではなく、setObject()というメソッドがあることに触れました。このメソッドが次回の主役です。コンポーネントに入力されたデータをデータ管理者たるモデルに渡す方法について、次回紹介します。そのために、簡単なtwitterクライアントを作成していきたいと思います。

おすすめ記事

記事・ニュース一覧