ソフトバンククリエイティブ
売り上げランキング: 61,798
オブジェクト指向における再利用のためのデザインパターンより、GoFのデザインパターンをJavaで実装してみる。 4回目の今回はガイダンスの”もっとも良く使われているパターン”からCompositeパターンを実装する。
Compositeパターンの主なメリットは以下のとおり。
- コンポーネントの種類に関する場合分けを取り除ける(透過性を高められる)
- 新しい種類のコンポーネントを簡単に追加できる
- 木構造の範囲内で複雑なオブジェクトを構成できる
Compositeパターンのクラス図は以下のとおり。
Component
は木構造の要素を表すクラスである。コンポーネントに共通の操作はすべてこのクラスに定義する。表現したいコンポーネントの種類だけ、Component
のサブクラスを作る。一例として、Leaf
は木構造の中で葉を表すサブクラスである。Composite
は複数のComponent
を束ねるサブクラスであり、このパターンの本質的なクラスである。Composite
は子オブジェクトとしてComponent
の集合を保持し、operation
が呼ばれたら、全ての子オブジェクトに対してoperation
を再帰的に実行する。
Client
はLeaf
とComposite
を組み合わせて木構造を構成する。またClient
は、Component
のインタフェースを通じて、木構造中の任意の要素について、operation
を呼び出すことができる。この際に、Client
は、operation
を呼び出したComponent
が、Leaf
であるのかComposite
なのか意識する必要はない。
子オブジェクトを管理するオペレーション(add
, remove
, getChild
)をComponent
で宣言するかComposite
で宣言するかはCompositeパターンを使う上で悩ましい点である。これはGoF本でも重要な問題とされている。
ただし、GoF本には以下の記載があり、デザインパターンの目的としてはComponent
を透過的に扱える事を重視しているように読める。よって、本エントリでは子オブジェクトを管理するオペレーションをComponent
に実装することにした。
このパターンでは安全性より透過性を強調してきた。安全性を重視する場合には、型情報を失うかもしれないが、componentをcompositeに変換しなければならないだろう。 (中略) もちろん、ここでの問題は、すべてのcomponentを一様に扱えないことである。つまり、特定のアクションを実行する前に、オブジェクトの型をテストするという状況に後戻りしなければならない。
Component
に子オブジェクトを管理するオペレーションを実装した都合上、Leaf
のクラスにデフォルト実装を提供したくなったため、以下のサンプルコードではComponent
を抽象クラスとして、Leaf
とComposite
は必要なメソッドをオーバーライドするようにした。よりJava風にするのならComponent
をインタフェースとして、デフォルト実装はAbstractLeaf
のような中間クラスを設けて提供する方法も考えられる。
Compositeパターンに関連するパターンは多い。木構造の要素を順にアクセスするにはIteratorパターンが利用できる。また要素を共有して資源を節約するFlyweightパターンが利用できる。さらに、もし要素に親オブジェクトへの参照を持たせれば、Chain of Responsibilityパターンを併用することになる。
- Client.java
package com.xmisao.gof.composite;
public class Client {
public static void main(String[] args) {
// building tree
//
// root
// / \
// leaf composite
// / \
// leaf composite
//
Component root = new Composite();
root.add(new Leaf());
root.add(new Composite());
root.getChild(1).add(new Leaf());
root.getChild(1).add(new Composite());
root.operation();
}
}
- Component.java
package com.xmisao.gof.composite;
public abstract class Component {
abstract public void operation();
public void add(Component component){
throw new UnsupportedOperationException();
}
public void remove(Component component){
throw new UnsupportedOperationException();
}
public Component getChild(int i){
return null;
}
}
- Leaf.java
package com.xmisao.gof.composite;
public class Leaf extends Component{
@Override
public void operation() {
System.out.println("This is Leaf.");
}
}
- Composite.java
package com.xmisao.gof.composite;
import java.util.ArrayList;
import java.util.List;
public class Composite extends Component{
private List<Component> components = new ArrayList<Component>();
@Override
public void operation() {
System.out.println("This is Composite.");
for(Component component : components){
component.operation();
}
}
@Override
public void add(Component component){
components.add(component);
}
@Override
public void remove(Component component){
components.remove(component);
}
@Override
public Component getChild(int i){
return components.get(i);
}
}
- 実行結果
This is Composite.
This is Leaf.
This is Composite.
This is Leaf.
This is Composite.