先日、National Public Radio の Car Talk を聞いていたのですが、毎週人気の放送で、電話者が自分の車について質問しています。 この番組のホストは、毎回の休憩前に、1-800-CAR-TALK (1-800-227-8255) に電話をかけるよう、発信者に要求します。 もちろん、「CAR TALK」の方が覚えやすい。「CAR TALK」という単語は、2つの単語で7桁を表す合成語だからだ。 人間は一般に、個々の構成要素よりも、合成物を扱う方が簡単だと感じる。 同じように、オブジェクト指向のソフトウエアを開発する場合、個々の部品を操作するのと同じように、合成物を操作するのが便利なことが多い。 この前提は、今回の Java デザイン パターンのトピックである Composite デザイン パターンの基本原則を表しています。
- The Composite pattern
- CompositeパターンとStruts Tiles
- 複雑なレイアウトを手作業で実装する
- 例1. 手作業で実装した複雑なレイアウト
- Implement complex layouts with JSP includes
- 例 2. JSPで実装した複雑なレイアウトは
- 例 3. header.jsp
- 例 5. content.jsp
- 例 6. footer.jsp
- StrutsTilesで複雑なレイアウトを実装する
- 例8でリストされています。 WEB-INF/tiles-defs.xml
- Example 9. header-footer-sidebar-layout.jsp
- StrutsTilesでCompositeパターンを使う
- 例 10. WEB-INF/tiles-defs.xml: Composite パターンを使用する
- 例 11 のサイドバーのレイアウトにカプセル化されています。 sidebar-layout.jsp
- 例12.サイドバーのレイアウト: 例12. flags.jsp
- Example 13. sidebar-links.jsp
- 最近はすべてComposites
- Homework
- 前回の宿題
- Example H1.flags.jsp
- 例 H2. WEB-INF/web.xml (抜粋)
- 例H3 にある、パス /flags と設定ファイル actions.FlagAction とをStrutsアクションにマップしてみました。 WEB-INF/struts-config.xml
- をリストアップしています。 WEB-INF/webapps/actions/FlagAction.java
- 例 H6. sidebar-links.jsp
- Example H7. header.jsp
- Example H8. content.jsp
- Example H9. footer.jsp
- 例 H10. WEB-INF/classes/resources_en.properties
- 例H11. WEB-INF/resources_zh.properties
The Composite pattern
Composite パターンに飛び込む前に、まず複合オブジェクトを定義しなければなりません:他のオブジェクトを含むオブジェクト。
Java 開発者は、プリミティブ オブジェクトを操作するのとまったく同じ方法でコンポジット オブジェクトを操作しなければならないことが多いため、Composite パターンを必要とするのです。 たとえば、線やテキストのようなグラフィック プリミティブは、描画、移動、サイズ変更を行う必要があります。 しかし、それらのプリミティブで構成された図面などのコンポジットに対しても、同じ操作を行いたい。 理想は、プリミティブとコンポジットを区別せず、全く同じように操作することです。 もし、プリミティブ オブジェクトとコンポジットを区別して、これら 2 種類のオブジェクトに対して同じ操作を実行しなければならないとしたら、コードはより複雑になり、実装、保守、拡張がより困難になります。
Design Patterns では、著者は Composite パターンを次のように説明しています。 Composite は、クライアントが個々のオブジェクトやオブジェクトの構成を統一的に扱うことを可能にします。
Composite パターンを実装するのは簡単です。 Compositeクラスは、プリミティブなオブジェクトを表す基底クラスを拡張する。 図1は、Compositeパターンの構造を示すクラス図である。 Compositeパターンのクラス図
図1のクラス図では、Design PatternのCompositeパターンの議論からクラス名を使用しています。 Component
はプリミティブなオブジェクトのベースクラス(あるいはインターフェース)、Composite
はコンポジットクラスを表している。 例えば、Component
クラスはグラフィックプリミティブの基底クラスを表し、Composite
クラスはDrawing
クラスを表すかもしれない。 図1のLeaf
クラスは具象プリミティブオブジェクトを表し、例えばLine
クラスやText
クラスが該当します。 Operation1()
とOperation2()
メソッドは、Component
とComposite
クラスの両方が実装するドメイン固有のメソッドを表します。
Composite
クラスは、コンポーネントのコレクションを保持します。 通常、Composite
メソッドはそのコレクションを反復し、コレクション内の各Component
に対して適切なメソッドを呼び出すことによって実装されます。 例えば、Drawing
クラスは draw()
メソッドを次のように実装します:
Component
クラスに実装されたすべてのメソッドに対して、Composite
クラスは上記の draw()
メソッドで示されるように、合成物の構成要素を反復する同じシグネチャのメソッドを実装しています。
Composite
クラスは Component
クラスを拡張しているので、コンポーネントを期待するメソッドにコンポジットを渡すことができます。 Composite
クラスは Component
を継承しているので、repaint()
メソッドはコンポーネントとコンポジットを区別する必要はなく、単にコンポーネント (またはコンポジット) の draw()
メソッドを呼び出すだけです。
図 1 の Composite パターン クラス図は、パターンの問題点をひとつ示しています:Component
を参照したときに、コンポーネントとコンポジット間を区別しなければならず、 addComponent()
など、コンポーネント特有のメソッドを呼び出さなければならない。 通常、Component
クラスにisComposite()
のようなメソッドを追加することで、この要件を満たします。 このメソッドはコンポーネントに対してfalse
を返し、Composite
クラスでtrue
を返すようにオーバーライドされる。 さらに、次のように Component
の参照を Composite
インスタンスにキャストする必要があります:
addComponent()
メソッドは Component
参照を渡されることに注意してください。参照はプリミティブ コンポーネントまたはコンポジットのいずれかにすることができます。 そのコンポーネントはコンポジットである可能性があるため、前述の Design Patterns からの引用で示されるように、コンポーネントをツリー構造に構成することができます。 代替のCompositeパターンのクラス図
図2のCompositeパターンを実装すると、コンポーネントとコンポジットを区別する必要は決してなく、Component
参照をComposite
インスタンスにキャストする必要はない。 つまり、上記のコードは 1 行になります。
...component.addComponent(someComponentThatCouldBeAComposite);...
しかし、前のコードの Component
参照が Composite
を参照していない場合、addComponent()
はどうすればよいのでしょうか。 これは、Figure 2のCompositeパターンの実装の大きな問題点です。 プリミティブ コンポーネントには他のコンポーネントが含まれないため、コンポーネントを他のコンポーネントに追加しても意味がなく、Component.addComponent()
メソッドは黙って失敗するか例外をスローします。 通常、コンポーネントを別のプリミティブ コンポーネントに追加することはエラーと見なされるので、例外をスローすることがおそらく最善の方法でしょう。 Design Patterns は、コンポーネントとコンテナを区別する必要がなく、キャストを実行する必要がないため、図 2 の実装を好みます。 個人的には、図 1 の実装を好みます。なぜなら、そのオブジェクト型にとって意味のないメソッドをクラスで実装することに強い嫌悪感を抱いているからです。
CompositeパターンとStruts Tiles
Apache StrutsフレームワークはTilesと呼ばれるJSPタグライブラリを含んでおり、複数のJSPからWebページを構成することができます。 Tiles は、実際には J2EE (Java 2 Platform, Enterprise Edition) の CompositeView パターンの実装であり、それ自体は Design Patterns の Composite パターンをベースにしています。 CompositeパターンとTilesタグ・ライブラリとの関連性を説明する前に、まず、Tilesの根拠とその使用方法について確認しておきましょう。 Struts Tilesに既に慣れている方は、以下のセクションを読み飛ばして、「Struts TilesでCompositeパターンを使う」から読み始めることができます。
注:J2EE CompositeViewパターンについては、私の記事「Web Application Components Made Easy with Composite View」(JavaWorld, December 2001)に詳しく書かれています。
Web サイトには、図3のサイドバー/ヘッダー/コンテンツ/フッターのレイアウトのように、同じレイアウトのWebページが複数あることがよくあります。 Struts Tilesを使うと、複数のWebページ間でコンテンツとレイアウトの両方を再利用することができます。
複雑なレイアウトを手作業で実装する
例1は、図3のWebページをHTMLで実装する方法を示しています:
例1. 手作業で実装した複雑なレイアウト
先の JSP には、2 つの大きな欠点があります。 まず、ページのコンテンツは JSP に埋め込まれているため、サイドバー、ヘッダー、フッターは多くの Web ページで同じである可能性があるにもかかわらず、そのいずれも再利用することができない。 第二に、ページのレイアウトもそのJSPに埋め込まれているので、同じWebサイトの他の多くのWebページが同じレイアウトを使用していても、同様に再利用することはできません。
Implement complex layouts with JSP includes
Example 2 は、<jsp:include>
を使用した図 3 の Web ページの実装を示しています:
例 2. JSPで実装した複雑なレイアウトは
先のJSPは<jsp:include>
で他のJSPの内容を含んでいます。 そのコンテンツを別の JSP にカプセル化したので、他の Web ページで再利用できます。
例 3. header.jsp
<font size='6'>Welcome to Sabreware, Inc.</font><hr>
例 5. content.jsp
<font size='4'>Page-specific content goes here</font>
<hr>Thanks for stopping by!
例 2 の JSP では <jsp:include>
でコンテンツを再利用しても、ページのレイアウトはその JSP でハードコードされているので、再利用できません。
StrutsTilesで複雑なレイアウトを実装する
例7は、図3のWebページをStruts Tilesで実装した例です。 Struts Tilesを使ってレイアウトをカプセル化する
先のJSPは、<tiles:insert>
タグを使って図3のJSPを作成しています。 そのJSPは、sidebar-header-footer-definition
というタイル定義によって定義されています。 その定義はTiles設定ファイルに存在し、この場合WEB-INF/tiles-defs.xml
で、例8:
例8でリストされています。 WEB-INF/tiles-defs.xml
先のTiles定義は、例3-6にリストされているように、header-footer-sidebar-layout.jsp
でカプセル化されたページレイアウトと、sidebar.jsp
、header.jsp
、content.jsp
、footer.jsp
でカプセル化されたページのコンテンツを指定します。 例9は、レイアウトheader-footer-sidebar-layout.jsp
:
先のJSPはレイアウトをカプセル化し、Tiles定義ファイル内のサイドバー、エディタ、コンテンツ、フッター領域に対して指定した値に従ってコンテンツを挿入するので、コンテンツとレイアウト両方のための再利用が容易になる。 例えば、同じレイアウト、同じサイドバー、エディター、フッターを持つ別のTiles定義を定義することができます。
Tiles定義a-different-sidebar-header-footer-definition
によって定義されたJSPを作成するには、次のように<tiles:insert>
タグを使用します:
StrutsTilesにより、コンテンツとレイアウトの両方を再利用できますので、レイアウトといくつかのコンテンツを共有している多くのJSPがあるWebサイトでは非常に有用であると証明されました。 しかし、例7-9のコードをよく見ると、サイドバー領域のレイアウトは、例3にリストされているsidebar.jsp
にハードコードされていることに気づくでしょう。 つまり、そのレイアウトを再利用することはできないのです。 幸い、Tiles タグライブラリは Composite パターンを実装しており、リージョンに対して JSP ではなくタイルの定義を指定することができます。
StrutsTilesでCompositeパターンを使う
Struts TilesはCompositeパターンを実装しており、Component
クラスはJSP、Composite
クラスはTiles定義で表現されます。 その実装では、JSPの領域のコンテンツとして、JSP(コンポーネント)かTilesの定義(コンポジット)を指定することができます。 例 10 はその機能を示しています:
例 10. WEB-INF/tiles-defs.xml: Composite パターンを使用する
先の Tiles 設定ファイルでは、2 つの Tiles 定義を定義しています。 sidebar-definition
とsidebar-header-footer-definition
です。 sidebar-definition
は sidebar-header-footer-definition
のサイドバー領域の値として指定されています。 このように指定できるのは、通常単一の JSP (これは Component
) を指定するところを、Tiles に定義 (JSP コレクションである Composite
) を指定させて Composite パターンを実装しているからです。
例 11 の sidebar-layout.jsp
:
例12は、サイドバーのtop
領域のコンテンツとして指定されたflags.jsp
をリストし、例13は、サイドバーのbottom
領域として指定されたsidebar-links.jsp
をリストする:
例12.サイドバーのレイアウト:
例12. flags.jsp
ここで、sidebar-definition
は上下のコンポーネントを持つ他の領域を定義できますが、その定義はおそらくtop-bottom-definition
のようなもっと一般的なものにリネームすべきです。
最近はすべてComposites
Compositeパターンは、コンポーネントとそのコンテナをまったく同じに扱うことによりコンテナを入れ子にできるので、Swing や Strutsなどのプレゼンテーション・フレームワークで人気です。 Struts TilesはCompositeパターンを使って、単純なJSPやJSPの集まりであるTilesの定義をタイルのコンテンツとして指定します。
以下の「前回の宿題」では、StrutsアクションとJSP Standard Tag Library (JSTL) を使って、前のアプリケーションを国際化することによって、この記事の議論を発展させています。
Homework
SwingがComponent
とContainer
クラスを使ってどのようにCompositeパターンを実装しているかについて議論してください。
前回の宿題
前回の課題では、http://jakarta.apache.org/struts/index.htmlからStrutsをダウンロードし、自分自身のStrutsアクションクラスを実装するよう求められました。
この課題では、例1のWebアプリケーションを国際化するためにJSP Standard Tag Library (JSTL) と連携してStrutsアクションを実装することに決めました。 StrutsはWebアプリケーションを国際化するために必要なインフラを提供していますが、JSTLは標準規格なので、その作業にはJSTLを使用する必要があります。 Strutsの国際化機能はいつか廃止されるか、JSTLに統合されるでしょう。
StrutsアクションとJSTLで例1のWebアプリケーションを国際化した後、そのアプリケーションを中国向けにローカライズしてみました。 図H1はその結果を示しています。
注意:私は中国語を一言も知らないし、その言語を書く方法も知らないので、図H1の中国語は任意のユニコード文字列から作られたものです。
Notice 私は例12のflags.jsp
のhref
属性を空の文字列として指定したので、フラグをクリックすると、サーブレットコンテナは現在のJSPを再読み込みします。 したがって、Webアプリケーションを国際化するための最初のステップは、例H1:
Example H1.flags.jsp
各href
属性に対するURLは同じです: flags.do
。 1 つはフラグに対応するロケール、もう 1 つは現在の JSP のパスを表しています。 後者のリクエストパラメータは、Http.ServletRequest.getServletPath()
メソッドを呼び出すことで取得できます。
アプリケーションの配置記述子では、.do
で終わるすべてのURLを、次のようにStrutsアクションサーブレットにマッピングしています:
例 H2. WEB-INF/web.xml (抜粋)
Note: StrutsとStrutsアクションサーブレットについての詳細は、私の記事 “Take Command of Your Software” (JavaWorld, June 2002) を参照してください。
次に例H3:
例H3 にある、パス /flags
と設定ファイル actions.FlagAction
とをStrutsアクションにマップしてみました。 WEB-INF/struts-config.xml
このマッピングにより、URL flags.do
はStrutsアクションサーブレットにactions.FlagAction.execute()
メソッドを呼び出させるので、フラグをクリックするとactions.FlagAction.execute()
メソッドが呼び出されることになります。 例H4では、actions.FlagAction
クラス:
をリストアップしています。 WEB-INF/webapps/actions/FlagAction.java
actions.FlagAction.execute()
メソッドはlocale
リクエストパラメータへの参照を取得し、その値をJSTL Config
クラスのset()
メソッドに渡します。 このメソッドは、ロケールを表す文字列を FMT_LOCALE
設定に保存し、JSTL がテキストのローカライズや数値・通貨・パーセント・日付のフォーマットを行うために使用します。
さて、JSTL の国際化動作にロケールを指定したので、次にアプリケーションの配置記述子の中でリソースバンドルを指定してみます。 WEB-INF/web.xml (Excerpt)
リソースバンドルのベースネーム resources
は、javax.servlet.jsp.jstl.fmt.localizationContext
コンテキスト初期化パラメーターに指定されています。 これは、ロケールが British English (en-GB
) に設定されている場合は JSTL が resources_en.properties
という名前のファイルを検索し、ロケールが Chinese (zh-ZH
) に設定されている場合は resources_zh.properties
を検索することを意味します。
さて、JSTL 国際化アクションにリソースバンドルとロケールが指定できたので、次は例 H6-H9 で示したように JSPファイルを修正してこれらのアクションを使ってみましょう:
Example H7. header.jsp
Example H8. content.jsp
Example H6-H9 の JSP は JSTL <fmt:message>
アクションを使って展開ディスクリプタで特定したリソースバンドルから Unicode 文字列を展開するようにしています。 例 H10 と H11 は、それぞれ英語と中国語のリソース バンドルをリストアップしています:
例 H10. WEB-INF/classes/resources_en.properties
例H11. WEB-INF/resources_zh.properties
Email Joseph Friedmanは私への電子メールで次のように書いています:
In “Decorate Your Java Code” (JavaWorld, December 2001), you write:
“The enclosing object-known as a decorator-cforms of the object it encloses, allowing it be used as an instance of the object it encloses “とあるように,デコレーション・デバイスはそのオブジェクトのインターフェースと適合しています.「
しかしながら、コード例では
FileReader
をLineNumberReader
で装飾し、FileReader
インターフェースにないreadLine()
を呼び出しています。 つまり、Reader
への参照を期待するあらゆるメソッドにデコレータを渡すことができるのです。 これらのメソッドは、デコレータが持つ特殊な機能 (この場合はファイルを読み込んで行番号を生成する機能) を知らないままです。Decorator が、装飾されたオブジェクトにない (1 つまたは複数の) メソッドを持っているという事実は、決して Decorator パターンの意図に反するものではありません。
Design Patterns ページ 173 を見ると、次のことがわかります:
Decorator サブクラスは、特定の機能のための操作を自由に追加することができます。 例えば、
ScrollDecorator
のScrollTo
操作は、他のオブジェクトがインターフェイスにScrollDecorator
オブジェクトがあることを知っていれば、インターフェイスをスクロールさせることができます。 18年にわたり、数多くのオブジェクト指向言語によるオブジェクト指向ソフトウェアの開発に従事。 1994年にGOFD Design Patternsを出版して以来、デザインパターンを積極的に提唱し、Smalltalk、C++、Javaでデザインパターンを使用、実装してきました。 1997年、Davidは著者として、また時折講演者やコンサルタントとしてフルタイムで働き始めた。 David は、JSP 標準カスタムタグライブラリおよび JavaServer Faces を定義する専門家グループのメンバーであり、Apache Struts JSP フレームワークの貢献者でもあります。