デザインパターン[Composite]

はいー 今日もデザパタでお送りします。

今日は、Composite。
こいつは超絶なすごさを持ってる。

そこらの本だと階層構造が・・・って話になるんだけど・・・
もっとすごいのよ。 Compositeは。
確かに階層構造やら再帰構造にはもってこいのパターンなんだけどね。

パターン指向リファクタリングの中では、
「Compositeパターンによる単数・浮く数別の処理の置き換え」
というのでまた別の視点からの使い方が紹介されている。

これを見たときは、ビビっときたのを今でも覚えている。

さて、Compositeがさっぱりという人のために、
ディレクトリ構造を表すCompositeパターンのコードをば。


public interface Node {
int getSize();
}

public class File implements Node {
private int _size;
public File(int size) {
_size = size;
}
public int getSize() {
return _size();
}
}

public class Folder implements Node {
private List _nodes;
public Folder() {
_nodes = new ArrayList(0);
}
public int getSize() {
int sum = 0;
for (int i = 0; i < _nodes.length; i++) {
sum += _nodes.get(i).getSize();
}
return sum;
}
public void add(Node node) {
_nodes.add(node);
}
}

これがフォルダ階層を表すConpositeだ。
NodeインターフェイスをFileとFolderが実装している。
Fileは単純にgetSize()でそのファイルサイズを返すが、
Folderはフィールドの_nodesのgetSize()を順番に行い、
合計を返す。
_nodesはNode型なのでFileとFolderどちらでも格納可能なので、
どんなに深い階層を作ったとしても、getSize()で得られる値はそれ以下の全ての合計となる。

これが一般的なCompositeの説明だろう。


ここからが面白い。

簡易にSQLを構築するクラスを作ってみよう。
select * from table;
の場合、selectオブジェクトを作り、テーブルを設定するメソッド、フィールドを設定するメソッドを作れば十分だろう。

まずは、テストコードだ。

public class SelectTest extends TestCase {
public void testSelectBasic() {
Select select = new Select("table");
select.addField("*");
assertEquals("select * from table", select.create());
}
}

こんな使い方でいいだろう。
ではSelectクラスの実装だ。

public class Select {
private String _tableName;
private List _fields;
public Select(String table) {
_tableName = table;
_fields = new ArrayList(0);
}
public void addField(String field) {
_fields.add(field);
}
public String create() {
String sql = "select " + getFields() + " from " + tableName;
}
private String getFields() {
String fields = "";
for (int i = 0; i < _fields.length; i++) {
fields += _fields[i];
if (_fields.length - 1 != i) {
fields += ", ";
}
}
return fields
}
}


こんなところだろう。やっと準備完了だ。

では、
whereの実装に入る。
一番簡単な
where field = '10';
を実装すると、
// ○=●を表現

class Expression {
private String _field;
private String _value;
private Expression(String field, String value)
・・・
}
public void setWhere(Expression ex)

のようなクラスとメソッドでOKだろう。

では、where field = '10' and field2 = '20'
はどう処理するか?
public void addWhere(Expression ex)
にして2回呼び出すことにしよう。

では、where field = '10' and field2 = '20' or field3 = '30'
ではどうだろうか?
こうなると、Selectクラス側で対応するのは骨が折れる。

そこでCompositeの登場だ。

public interface Expression {
}
public class Equal implements Expression {
}
public class And implements Expression {
}
public class Or implements Expression {
}

Equalクラスは先のExpressionクラスだ。
Expressionという名前は汎用的すぎるので、interface名をそれにした。
Equalが 一番最初に説明したComsositeのFileに当たる。
And、OrクラスがFolderに相当するクラスだ。
where field = '10' and field2 = '20' or field3 = '30'
を表現するのは

Equal field = new Equal("field", "10");
Equal field2 = new Equal("field2", "20");
Equal field3 = new Equal("field3", "30");
And andObj = new And(field, field2);
Or orObj = new Or(andObj, field3);

となる。

これでかなり汎用的にwhereが作れる。

この例では複数・単数の置き換えにはなっているようないないようななので、詳しくはパターン指向リファクタリング入門を見て欲しいが、
非常に複雑なクエリでも適切にCompositeに当てはめてやるだけで
簡単に作ることができる。

これがCompositeのすごいところだ。

ちなみにHibernateのCriteriaのRestructionsクラスも同じような実装になっている。 非常に使い勝手が良い。