GoFのデザインパターンをJavaで実装して理解する Composite編
タグ: GoFjava / 初版公開: 2014-02-10
オブジェクト指向における再利用のためのデザインパターン
エリック ガンマ ラルフ ジョンソン リチャード ヘルム ジョン ブリシディース
ソフトバンククリエイティブ
売り上げランキング: 61,798

オブジェクト指向における再利用のためのデザインパターンより、GoFのデザインパターンをJavaで実装してみる。 4回目の今回はガイダンスの”もっとも良く使われているパターン”からCompositeパターンを実装する。

Compositeパターンの主なメリットは以下のとおり。

  • コンポーネントの種類に関する場合分けを取り除ける(透過性を高められる)
  • 新しい種類のコンポーネントを簡単に追加できる
  • 木構造の範囲内で複雑なオブジェクトを構成できる

Compositeパターンのクラス図は以下のとおり。

Composite

Componentは木構造の要素を表すクラスである。コンポーネントに共通の操作はすべてこのクラスに定義する。表現したいコンポーネントの種類だけ、Componentのサブクラスを作る。一例として、Leafは木構造の中で葉を表すサブクラスである。Compositeは複数のComponentを束ねるサブクラスであり、このパターンの本質的なクラスである。Compositeは子オブジェクトとしてComponentの集合を保持し、operationが呼ばれたら、全ての子オブジェクトに対してoperationを再帰的に実行する。

ClientLeafCompositeを組み合わせて木構造を構成する。またClientは、Componentのインタフェースを通じて、木構造中の任意の要素について、operationを呼び出すことができる。この際に、Clientは、operationを呼び出したComponentが、LeafであるのかCompositeなのか意識する必要はない。

子オブジェクトを管理するオペレーション(add, remove, getChild)をComponentで宣言するかCompositeで宣言するかはCompositeパターンを使う上で悩ましい点である。これはGoF本でも重要な問題とされている。

ただし、GoF本には以下の記載があり、デザインパターンの目的としてはComponentを透過的に扱える事を重視しているように読める。よって、本エントリでは子オブジェクトを管理するオペレーションをComponentに実装することにした。

このパターンでは安全性より透過性を強調してきた。安全性を重視する場合には、型情報を失うかもしれないが、componentをcompositeに変換しなければならないだろう。 (中略) もちろん、ここでの問題は、すべてのcomponentを一様に扱えないことである。つまり、特定のアクションを実行する前に、オブジェクトの型をテストするという状況に後戻りしなければならない。

Componentに子オブジェクトを管理するオペレーションを実装した都合上、Leafのクラスにデフォルト実装を提供したくなったため、以下のサンプルコードではComponentを抽象クラスとして、LeafCompositeは必要なメソッドをオーバーライドするようにした。より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.