2013年5月17日金曜日

design patterns – Composite


コンポジット

シングルトンパターンの説明を読んで、「簡単だなぁ」と感じた方もどうかご心配なく。次は、もう少し複雑なパターンを2つ説明します。まず1つはコンポジットパターンです。コンポジットは、その名のとおり、1つのエンティテイを形成する複数のパーツで構成されるオブジェクトのことです。この1つのエンティテイは、すべてのパーツのアクセスポイントとして機能します。これを使うと、コードが非常にシンプルになりますが、コンポジットに含まれるパーツの数を把握する間接的な方法がないため、当てにならない面もあります。

コンポジットの構造

コンポジットの説明には図解が一番です。図1には、2つのタイプのオブジェクトがあります。コンテナとギャラリーはコンポジット、画像はリーフ(葉)です。コンポジットは子を持つことができますが、通常はあまり多くの挙動を実装しません。リーフには挙動の大半が含まれますが、少なくとも、従来のコンポジットの例では子を持つことはできません。
図1.コンポジットの構造
図1.コンポジットの構造
もう1つ、現実にあるコンポジットパターンで、皆さんがそうとは知らずに使っていたと思われる例をご紹介しましょう。コンピューターのファイル構造は、コンポジットパターンの一例です。例えば、フォルダーを削除すると、その中身もすべて削除されますが、これは本質的にコンポジットパターンの動作そのものです。ツリー構造の上部のコンポジットオブジェクトに対してメソッドを呼び出すことができ、メッセージは階層の下方へ伝達されます。

コンポジットコーディングの例

この例では、コンポジットパターンのサンプルとして画像ギャラリーを作成します。アルバム、ギャラリー、画像という3レベルの階層構造になります。図1の構成で言うと、アルバムとギャラリーはコンポジットで、画像はリーフに相当します。この構造は、コンポジットに必要とされるよりも明確な構造ですが、この例では、階層をコンポジットまたはリーフのみに制限した方が理にかなっています。標準的なコンポジットでは、リーフにできる階層レベルについて制限はありませんし、レベル数の制限もありません。
手始めに、アルバムとギャラリーの両方に使用されるGalleryCompositeという「クラス」を作成します。DOM操作にはjQueryを使用してコードを簡素にしていることに着目してください。
var GalleryComposite = function (heading, id) { this.children = []; this.element = $(' ') .append('

' + heading + '

'); } GalleryComposite.prototype = { add: function (child) { this.children.push(child); this.element.append(child.getElement()); }, remove: function (child) { for (var node, i = 0; node = this.getChild(i); i++) { if (node == child) { this.children.splice(i, 1); this.element.detach(child.getElement()); return true; } if (node.remove(child)) { return true; } } return false; }, getChild: function (i) { return this.children[i]; }, hide: function () { for (var node, i = 0; node = this.getChild(i); i++) { node.hide(); } this.element.hide(0); }, show: function () { for (var node, i = 0; node = this.getChild(i); i++) { node.show(); } this.element.show(0); }, getElement: function () { return this.element; } }
このコード例は長いので、ここで簡単に説明しておきましょう。addremovegetChildの各メソッドが、コンポジットの構築に使用されています。この例では、実際にはremoveおよびgetChildは使用されませんが、この2つのメソッドは動的なコンポジットの作成に役立ちます。hideshowgetElementの各メソッドは、DOMの操作に使用されます。このコンポジットは、ユーザーに表示されるページ上のギャラリーの表現として設計されています。コンポジットは、hideshowを介してギャラリー要素をコントロールできます。アルバムに対してhideを呼び出すと、アルバム全体が非表示になります。1枚の画像に対してのみ呼び出した場合は、その画像だけが非表示になります。
次に、GalleryImageクラスを作成します。このクラスでは、GalleryCompositeとまったく同じメソッドを使用することに着目してください。つまり、どちらのクラスも同じインターフェイスを実装します。ただし、画像はリーフであり、子を持つことはできないため、GalleryImageクラスは子に関するメソッドに対しては実際には何もしません。コンポジットを機能させるには、同じインターフェイスを使用する必要があります。コンポジット要素は、追加しようとする要素が別のコンポジット要素であるかリーフであるかを把握していないので、子に対してこれらのメソッドを呼び出そうとしたときにエラーにならないよう正常に機能させる必要があるからです。
var GalleryImage = function (src, id) { this.children = []; this.element = $('') .attr('id', id) .attr('src', src); } GalleryImage.prototype = { // Due to this being a leaf, it doesn't use these methods, // but must implement them to count as implementing the // Composite interface add: function () { }, remove: function () { }, getChild: function () { }, hide: function () { this.element.hide(0); }, show: function () { this.element.show(0); }, getElement: function () { return this.element; } }
以上で、オブジェクトのプロトタイプを作成できたので使ってみましょう。以下は、画像ギャラリーを実際にビルドするコードです。
var container = new GalleryComposite('', 'allgalleries'); var gallery1 = new GalleryComposite('Gallery 1', 'gallery1'); var gallery2 = new GalleryComposite('Gallery 2', 'gallery2'); var image1 = new GalleryImage('image1.jpg', 'img1'); var image2 = new GalleryImage('image2.jpg', 'img2'); var image3 = new GalleryImage('image3.jpg', 'img3'); var image4 = new GalleryImage('image4.jpg', 'img4'); gallery1.add(image1); gallery1.add(image2); gallery2.add(image3); gallery2.add(image4); container.add(gallery1); container.add(gallery2); // Make sure to add the top container to the body, // otherwise it'll never show up. container.getElement().appendTo('body'); container.show();
以上が、コンポジットに関する説明です。この画像ギャラリーの実際のデモは、筆者のブログのデモページでご覧いただけます。また、筆者のブログの「JavaScript Design Patterns: Composite」という記事では、このパターンに関してもう少し詳しく説明しています。

0 件のコメント: