#はじめに
詳しいことや他のパターンは**デザインパターンをJavaScriptとJavaでの実装を比較して理解する**に書いていきます。
JavaScriptの例はJavaのを見て書きました。
クラス型・プロトタイプ型、型付の強弱、アクセス修飾子など特徴の違いなどは活かしていません。
ご了承ください。
#Compositeパターン
コンピュータのファイルシステムには「ディレクトリ」というものがある
ディレクトリの中には別のディレクトリやファイルが入っている
ディレクトリはそのような「入れ子」になった構造、再帰的な構造を作り出す
ディレクトリとファイルをまとめてディレクトリエントリとして扱うように、
容器と中身を同じ種類のものとして扱うと便利な場合がある
容器と中身を同一視し、再帰的な構造を作るパターンをCompositeパターンと呼ぶ
compositeは「混合物」「複合物」という意味
##Javaでの実装例
ファイルとディレクトリを模式的に表現したプログラム
###クラス図
###コード
public class Main {
public static void main(String[] args) {
try {
System.out.println("Making root entries...");
Directory rootdir = new Directory("root");
Directory bindir = new Directory("bin");
Directory tmpdir = new Directory("tmp");
Directory usrdir = new Directory("usr");
rootdir.add(bindir);
rootdir.add(tmpdir);
rootdir.add(usrdir);
bindir.add(new File("vi", 10000));
bindir.add(new File("latex", 20000));
rootdir.printList();
System.out.println("");
System.out.println("Making user entries...");
Directory yuki = new Directory("yuki");
Directory hanako = new Directory("hanako");
Directory tomura = new Directory("tomura");
usrdir.add(yuki);
usrdir.add(hanako);
usrdir.add(tomura);
yuki.add(new File("diary.html", 100));
yuki.add(new File("Composite.java", 200));
hanako.add(new File("memo.tex", 300));
tomura.add(new File("game.doc", 400));
tomura.add(new File("junk.mail", 500));
rootdir.printList();
} catch (FileTreatmentException e) {
e.printStackTrace();
}
}
}
public abstract class Entry {
public abstract String getName();
public abstract int getSize();
public Entry add(Entry entry) throws FileTreatmentException {
throw new FileTreatmentException();
}
public void printList() {
printList("");
}
protected abstract void printList(String prefix);
public String toString() {
return getName() + " (" + getSize() + ")";
}
}
import java.util.Iterator;
import java.util.ArrayList;
public class Directory extends Entry {
private String name;
private ArrayList<Entry> directory = new ArrayList<Entry>();
public Directory(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int getSize() {
int size = 0;
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry)it.next();
size += entry.getSize();
}
return size;
}
public Entry add(Entry entry) {
directory.add(entry);
return this;
}
protected void printList(String prefix) {
System.out.println(prefix + "/" + this);
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry)it.next();
entry.printList(prefix + "/" + name);
}
}
}
public class File extends Entry {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
public String getName() {
return name;
}
public int getSize() {
return size;
}
protected void printList(String prefix) {
System.out.println(prefix + "/" + this);
}
}
public class FileTreatmentException extends RuntimeException {
public FileTreatmentException() {
}
public FileTreatmentException(String msg) {
super(msg);
}
}
##JavaScriptでの実装例
###コード
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Composite</title>
</head>
<body>
<script src="Main.js"></script>
<script src="Directory.js"></script>
<script src="File.js"></script>
</body>
</html>
MAIN = {};
MAIN.init = function() {
console.log("Making root entries...");
MAIN.rootdir = new Directory("root");
MAIN.bindir = new Directory("bin");
MAIN.tmpdir = new Directory("tmp");
MAIN.usrdir = new Directory("usr");
MAIN.rootdir.add(MAIN.bindir);
MAIN.rootdir.add(MAIN.tmpdir);
MAIN.rootdir.add(MAIN.usrdir);
MAIN.bindir.add(new File("vi", 10000));
MAIN.bindir.add(new File("latex", 20000));
MAIN.rootdir.printList();
console.log("");
console.log("Making user entries...");
MAIN.yuki = new Directory("yuki");
MAIN.hanako = new Directory("hanako");
MAIN.tomura = new Directory("tomura");
MAIN.usrdir.add(MAIN.yuki);
MAIN.usrdir.add(MAIN.hanako);
MAIN.usrdir.add(MAIN.tomura);
MAIN.yuki.add(new File("diary.html", 100));
MAIN.yuki.add(new File("Composite.java", 200));
MAIN.hanako.add(new File("memo.tex", 300));
MAIN.tomura.add(new File("game.doc", 400));
MAIN.tomura.add(new File("junk.mail", 500));
MAIN.rootdir.printList();
};
window.addEventListener("load", MAIN.init);
var Directory = function(name) {
this.name = name;
this.directory = [];
};
Directory.prototype = {
constructor: Directory,
getName: function() {
return this.name;
},
getSize: function() {
var size = 0;
for (var i = 0, max = this.directory.length; i < max; i++) {
size += this.directory[i].getSize();
}
return size;
},
add(entry) {
this.directory.push(entry);
return this;
},
printList: function(prefix) {
if (arguments.length === 0) {
console.log("/" + this.getName() + " (" + this.getSize() + ")");
for (var i = 0, max = this.directory.length; i < max; i++) {
this.directory[i].printList(this.getName());
}
} else {
console.log(prefix + "/" + this.getName() + " (" + this.getSize() + ")");
for (var i = 0, max = this.directory.length; i < max; i++) {
this.directory[i].printList(prefix + "/" + this.getName());
}
}
}
}
var File = function(name, size) {
this.name = name;
this.size = size;
};
File.prototype = {
constructor: File,
getName: function() {
return this.name;
},
getSize: function() {
return this.size;
},
printList(prefix) {
console.log(prefix + "/" + this.getName() + " (" + this.getSize() + ")");
}
};
##Compositeパターンの登場人物
###Leaf(葉)の役
「中身」を表す役
この役の中には、他のものを入れることができない
サンプルプログラム⇒File(class)
###Composite(集合体)の役
「容器」を表す役
Leaf役やComposite役を入れることができる
サンプルプログラム⇒Directory(class)
###Componentの役
Leaf役とComposite役を同一視するための役
Component役は、Leaf役とComposite役に共通のスーパークラスとして表現する
サンプルプログラム⇒Entry(abstract class)
###Client(依頼者)の役
Compositeパターンの利用者
サンプルプログラム⇒Main(class)
##Compositeパターンのクラス図
##Compositeパターンの必要性
// Directory
public int getSize() {
int size = 0;
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry)it.next();
size += entry.getSize();
}
return size;
}
// File
public int getSize() {
return size;
}
どちらの場合でも、同じメソッドgetSizeでサイズを得ることができます。
これがCompositeパターンの特徴、「容器と中身を同じものとみなす」ことの現れです。
木構造を再帰的に呼び出してサイズを取得することができる
一般に木構造になるデータ構造はCompositeパターンに当てはまる
- ディレクトリシステム
- ウィンドウシステム
- 箇条書きの中に箇条書き
- マクロコマンド
##Compositeパターンの使い時
テストプログラムでCompositeパターンなどが使われる
- キーボードからの入力テスト
- ファイルからの入力テスト
- ネットワークからの入力テスト
これらの3つを入力テストとしたいときにCompositeパターンを使うことができる
##関連しているパターン
- Commandパターン
- Visitorパターン
- Decoratorパターン