Composite。
例えば自分のサイトのディレクトリ構造を考える。
このデータ構造をOOPっぽく表してみよう。
まず最初に考えるべき事は、「ディレクトリ([dir])とファイルってほとんど同じものだよね」と言うことだ。例えばプロパティを考えるとしたら「名前」や「ファイルサイズ(ディレクトリの場合は中身の合計)」など、基本的なものは同一である。むしろ唯一の違いは「ディレクトリはファイルを中身に持つ」と言うことだけだ。ってことは、共通部分を括り出せば効率的なコーディングが可能になるだろう。
ところで、上のディレクトリツリーは木構造になっているけども、こういった木構造のデータは他にも沢山ある。例えば組織図やBBSのスレッド、DOM等々。中身(ファイル)と入れ物(ディレクトリ)を同じものとして扱うことにより、こういった木構造を効率的に作っていくのがCompositeパターンだ。
// まずは共通部分を括りだしたクラスを作る
function Item() {
this.name; // 名前
// 名前を返すメソッド
function Item.prototype.getName() {
return this.name;
}
// サイズを返すメソッド(ファイルとディレクトリでは微妙に違うのでAbstractにしておく)
function Item.prototype.getSize() {
// abstract
}
}
// ファイル用のクラス
function File(newName, newSize) {
this.name = newName;
this.size = newSize; // サイズ保持用の変数
// サイズを返すメソッドを実装
function File.prototype.getSize() {
return this.size;
}
}
File.prototype = new Item() // Item クラスを継承
// ディレクトリ用のクラス
function Directory(newName) {
this.name = newName;
this.children = new Array() // 中身を保持するための配列
// サイズを返すメソッドを実装
function Directory.prototype.getSize() {
var total = 0;
for (var i = 0; i < this.children.length; i++) {
total += this.children[i].getSize();
}
return total;
}
// 新しい中身を追加するメソッド
function Directory.prototype.add(item) {
this.children.push(item);
}
// 中身を返すメソッド。面倒なのでIteratorパターンは使わない
function Directory.prototype.getChildren() {
return this.children;
}
}
Directory.prototype = new Item() // Item クラスを継承
// デモコード
var root = new Directory("root");
root.add(new File("index.html", 2));
root.add(new File("style.css", 3));
var about = new Directory("about");
about.add(new File("index.html", 3));
root.add(about);
var blog = new Directory("blog");
blog.add(new File("index.html", 4));
blog.add(new File("latest.rss", 3));
root.add(blog);
var b2005 = new Directory("2005");
b2005.add(new File("01.html", 8));
b2005.add(new File("02.html", 5));
blog.add(b2005);
// rootディレクトリの中身とサイズの関係を出力してみる。
var rootChildren = root.getChildren();
for (var i = 0; i < rootChildren.length; i++) {
print(rootChildren[i].getName() + " -- " + rootChildren[i].getSize() + "KB"); // print は適当に定義すべし
}
// Result:
// index.html -- 2KB
// style.css -- 3KB
// about -- 3KB
// blog -- 20KB
名前を返してくれるgetName()メソッドはItemクラスで定義されているため、File、Directoryクラスで定義し直す必要が無く、とても効率的だ。さらに、上のデモコードで強調されている部分、print(rootChildren[i].getName() + " -- " + rootChildren[i].getSize() + "KB");を見て欲しい。print関数が「rootChildren[i]はFileクラスかDirectoryクラスか」に無関心なのが分かるだろうか。この2つのクラスは同一視されているため、getName()とgetSize()というメソッドさえ分かっていれば、データを扱う側は違いを意識せずに扱うことが出来るのだ。この「同一視」による単純化がCompositeパターンの1番の肝になっている。
Javaの場合は型についても適度に無関心になる必要性があるため、継承が必須となるが、JavaScriptの場合はメソッド名さえ統一しておけば、Itemクラスの継承は必ずしも必要ではない(例えばgetName()もAbstractにしてしまった場合、結局全てのメソッドを定義し直す事になる)。getName()のような同じコードがある場合は継承し、無ければメソッド名を統一するだけも問題ないだろう。
ところで、なんだかこのシリーズは微妙に反響があるようなのだけども、読んで理解できるのか心配になってきた。「なぜこのパターンを使うのか」という説明は真面目にしてるつもりなのだけども、「Abstract」とかJavaScriptには無い概念を使っていたりして、本当に初めての人にはわかりにくいかも知れない。ある程度他の言語でOOPの知識がある人用かなあ。正直なところ言語レベルの話として、Javaほどにはデザインパターンが有効活用できない面もあるし、やっぱりOOPの勉強をするならJavaが良いと思う次第。あ、そうだ、「PHP5でデザインパターン」でもやろうか。かなり需要ありそうな予感。
http://yudai.arielworks.com/memo/2005/02/25/080622.trackback
末尾に「2 + 8」の計算結果を繋げて下さい。例えば計算結果が「17」の場合、「080622.trackback17」です。これは機械的なトラックバックスパムを防止するための措置です。