今日の目標
JavaのCompositeパターンとはどういったものなのかを知る
使うもの
ではスタート
はじめに
まずはCompositeパターンとはどのようなものなのかをWikipediaさんで調べてみる。
Composite パターン(コンポジット・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義された デザインパターンの1つである。「構造に関するパターン」に属する。Composite パターンを用いるとディレクトリとファイルなどのような、木構造を伴う再帰的なデータ構造を表すことができる。
Composite パターンにおいて登場するオブジェクトは、「枝」と「葉」であり、これらは共通のインターフェースを実装している。そのため、枝と葉を同様に扱えるというメリットがある。(wikipedia-Compositeパターン)
サンプルにもありましたが、ディレクトリとファイルを思い浮かべるとわかりやすいですね。ディレクトリ(枝)にはファイル(葉)とディレクトリ(枝)を含むことができます。そゆことです。
本のサンプルを試してみる
ということで、サンプルを試してみます。サンプルはディレクトリとファイルの構造を模したものを作る、というものです。登場人物は下記のとおり。
- Entry
- FileとDirectoryの抽象クラス
- File
- Fileクラス。Entryを実装してます
- Directory
- Directoryクラス。Entryを実装してます
- ディレクトリの中にディレクトリを含むことができます。表示やサイズ計算に再起的呼び出しが使われています
最初はEntryクラス。
package composite;
import exception.FileTreatmentException;
/**
* ディレクトリエントリを表現するクラス
* @author torinist
*
*/
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("");
}
// prefixを前につけて一覧を表示する
protected abstract void printList(String prefix);
// 文字列表現
public String toString() {
return getName() + " (" + getSize() + ")";
}
}
続いてFileクラス。Fileクラスは単純です。自分自身の名前とサイズを持って返すだけ。
package composite;
public class File extends Entry {
private String name;
private int size;
// コンストラクタ
public File(String name, int size) {
this.name = name;
this.size = size;
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
return size;
}
@Override
protected void printList(String prefix) {
System.out.println(prefix + "/" + this);
}
}
次はDirectoryクラス。Directoryクラスはその中にDirectoryクラスを持つことができます。DirectoryクラスのインスタンスにDirectoryクラスのインスタンスをaddすると、そのインスタンスのdirectoryフィールドに追加されます。printListやgetsizeをするときには、directoryフィールドの中身を見て、そこにオブジェクトがあったらオブジェクトに対してメソッドを呼び出すという処理をしてます。そうすることによって、ディレクトリの中のディレクトリを処理してるのですね。なるほど。
package composite;
import java.util.ArrayList;
public class Directory extends Entry {
private String name;
private ArrayList<Entry> directory = new ArrayList();
public Directory(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
// directory内にあるファイルのサイズを足して返す
// ディレクトリだった場合は再起処理
@Override
public int getSize() {
int size = 0;
for(Entry e : directory) {
size += e.getSize();
}
return size;
}
public Entry add(Entry entry) {
directory.add(entry);
return this;
}
@Override
protected void printList(String prefix) {
System.out.println(prefix + "/" + this);
for(Entry e : directory) {
e.printList(prefix + "/" + name);
}
}
}
おまけてきなException。Fileクラスに何か追加しようとするとExceptionが発生します(ファイルにファイルやディレクトリを入れることはできないですからねえ)
package exception;
/**
* ファイルに対する処理例外
* @author torinist
*
*/
public class FileTreatmentException extends RuntimeException {
public FileTreatmentException() {}
public FileTreatmentException(String msg) {
super(msg);
}
}
最後に実行クラス。Mainクラスではいくつかのディレクトリとファイルを作って、入れ子構造にしてます。大事なのは、Directoryインスタンス(nist)がFileインスタンス(readme.txt)もDirectoryインスタンス(workspace)も同じように処理している、ということですね。どちらもaddメソッドで対応してます。
import composite.Directory;
import composite.File;
import exception.FileTreatmentException;
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 usrdir = new Directory("usr");
rootdir.add(bindir);
rootdir.add(usrdir);
bindir.add(new File("vi", 10000));
bindir.add(new File("latex", 20000));
rootdir.printList();
System.out.println("\nMaking user entries...");
Directory tori = new Directory("tori");
Directory nist = new Directory("nist");
usrdir.add(tori);
usrdir.add(nist);
tori.add(new File("index.html", 100));
tori.add(new File("main.html", 3000));
nist.add(new File("readme.txt", 100));
Directory workspace = new Directory("workspace");
workspace.add(new File("index.html", 200));
nist.add(workspace);
rootdir.printList();
} catch(FileTreatmentException e) {
e.printStackTrace();
}
}
}
結果。
Making root entries...
/root (30000)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/usr (0)
Making user entries...
/root (33400)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/usr (3400)
/root/usr/tori (3100)
/root/usr/tori/index.html (100)
/root/usr/tori/main.html (3000)
/root/usr/nist (300)
/root/usr/nist/readme.txt (100)
/root/usr/nist/workspace (200)
/root/usr/nist/workspace/index.html (200)
作ったファイルとディレクトリが全部表示されました。
なかなかディレクトリとファイル以外に思いつかないのですが…。
Compositeは容器の中に容器を入れる(今回の場合はディレクトリのなかにディレクトリ)ので、必ず再起的呼び出しが出てきそうです。再起苦手な私としてはCompositeをマスターして再起克服したいですね!