Compositeパターン
ツリー構造のオブジェクトを階層の深さに関わらず一律に扱うためのデザインパターン。
個々のオブジェクト(葉、リーフ、Leafと呼ばれる)と複合オブジェクト(複合、コンポジット、Compositeと呼ばれる)を同一視し、クライアントが両者を同じように扱う。
ツリー構造を構成する要素はノード、Nodeと呼ばれ、子要素を持たない要素をリーフ、Leafと呼ぶ。
Iteratorパターンを内部で使用することで実現する。
透過性
透過性とは、コンポーネントが他の部分に対して内部の実装を隠し、簡潔で使いやすいインターフェースを提供することを指す。
Compositeパターンでは、クライアントは扱う対象がノードなのか、リーフなのかを意識しなくて良い(クライアントからは見えない=透過)。
(厳密にはクライアントは、コンポジットにしか利用できない、子要素を追加したり削除するメソッドをリーフに対して使用できない。そのため、透過的といっても、両者を全く意識しなくて良いわけではない。デザインパターンを取り入れる上で、メリットとデメリットのトレードオフの関係を受け入れる必要がある。)
-
情報の隠蔽
内部の実装、データ構造などの詳細部分を他のコードから隠蔽すること。外部に対しては、必要な情報だけが公開され、内部の実装の変更が外部に影響を与えないようになる。 -
インターフェースの明確化
明確で使いやすいインターフェースを提供すること。
透過性を実現することでシステムはモジュール化されるため、保守性が高まり、個々のコンポーネントが独立して開発、拡張できるようになる。
Compositeパターンが利用できるシチュエーション
例えば、このような組織図において、部署を管理するシステムを構築するものとする。
必要な抽象クラス
public abstract class DepartmentComponent {
// ------- ノードが担当する処理 -------
void showDescription() {
throw new UnsupportedOperationException();
}
// ------- ツリー構造管理のための処理 -------
void add(DepartmentComponent component) {
throw new UnsupportedOperationException();
}
void remove(DepartmentComponent component) {
throw new UnsupportedOperationException();
}
DepartmentComponent getChild(int index) {
throw new UnsupportedOperationException();
}
}
重要な考え方として、ComponentはLeafとしてもCompositeとしても扱える必要がある。(以下のインターフェースは実際には不要)
// 実際には不要
public interface LeafDepartment {
// ------- ノードが担当する処理 -------
void showDescription();
}
// 実際には不要
public interface CompositeDeparment {
// ------- ノードが担当する処理 -------
void showDescription();
// ------- ツリー構造管理のための処理 -------
void add(DepartmentComponent component);
void remove(DepartmentComponent component);
DepartmentComponent getChild(int index);
}
抽象クラスの継承
public class LeafDepartment extends DepartmentComponent {
String name;
int staffCount;
public LeafDepartment(String name, int staffCount) {
this.name = name;
this.staffCount = staffCount;
}
// ------- ノードが担当する処理 -------
@Override
void showDescription() {
System.out.println(name + "は、" + staffCount + "人で構成される組織です。");
System.out.println("----------------------");
}
// 下部組織を持たないため、ツリー構造の管理メソッドはオーバーライドしない
// ------- ツリー構造管理のための処理 -------
}
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class CompositeDeparment extends DepartmentComponent {
// 子要素をリストとして保持している
List<DepartmentComponent> childDepartments = new ArrayList<>();
String name;
int staffCount;
public CompositeDeparment(String name, int staffCount) {
this.name = name;
this.staffCount = staffCount;
}
// ------- ノードが担当する処理 -------
@Override
void showDescription() {
System.out.println(name + "は、" + staffCount + "人で構成される組織です。");
System.out.println("----------------------");
// イテレータを使って子要素を反復処理
Iterator it = childDepartments.iterator();
while (it.hasNext()) {
DepartmentComponent childDepartment = (DepartmentComponent) it.next();
childDepartment.showDescription();
}
}
// ------- ツリー構造管理のための処理 -------
@Override
void add(DepartmentComponent department) {
childDepartments.add(department);
}
void remove(DepartmentComponent department) {
childDepartments.remove(department);
}
DepartmentComponent getChild(int i) {
return childDepartments.get(i);
}
}
Compositeパターンの利用
クライアントは、DepartmentComponent
の各要素がノード(子要素を持つ)なのか、リーフ(子要素を持たない)なのかを意識していない。
public class Client {
DepartmentComponent allDepartments;
public Client(DepartmentComponent allDepartments){
this.allDepartments = allDepartments;
}
void descriptionDepartment(){
allDepartments.showDescription();
}
}
public class Main {
public static void main(String[] args){
DepartmentComponent allDepartments = new CompositeDeparment("株式会社Java", 100);
DepartmentComponent generalAffair = new CompositeDeparment("総務部", 20);
DepartmentComponent humanResource = new CompositeDeparment("人事部", 10);
DepartmentComponent sales = new CompositeDeparment("営業部", 10);
allDepartments.add(generalAffair);
allDepartments.add(humanResource);
allDepartments.add(sales);
DepartmentComponent salesSection1 = new CompositeDeparment("第一営業課", 20);
DepartmentComponent salesSection2 = new CompositeDeparment("第二営業課", 20);
sales.add(salesSection1);
sales.add(salesSection2);
// 子要素を持たないノードはリーフとして扱う
DepartmentComponent salesTeam1 = new LeafDepartment("営業一係", 10);
DepartmentComponent salesTeam2 = new LeafDepartment("営業二係", 10);
salesSection1.add(salesTeam1);
salesSection1.add(salesTeam2);
Client client = new Client(allDepartments);
client.descriptionDepartment();
// 株式会社Javaは、100人で構成される組織です。
// ----------------------
// 総務部は、20人で構成される組織です。
// ----------------------
// 人事部は、10人で構成される組織です。
// ----------------------
// 営業部は、10人で構成される組織です。
// ----------------------
// 第一営業課は、20人で構成される組織です。
// ----------------------
// 営業一係は、10人で構成される組織です。
// ----------------------
// 営業二係は、10人で構成される組織です。
// ----------------------
// 第二営業課は、20人で構成される組織です。
// ----------------------
}
}
ポイント
- Cpmpositeパターンは、
階層の管理・・・add()
、remove()
、getChild()
個々のオブジェクトの操作・・・operation()
という二つの責務を併せ持つため、一見すると単一責務の設計原則(Single Responsibility Principle)に反しているようにも見えるが、二つの責務はCompositeパターン全体の目的である「構造を表現し、クライアントに対して透過的なインターフェースを提供する」という責務に統合されている。 - コンポーネントは、リーフでもあり、コンポジットでもある。