Javaにおけるコンポジットパターン:柔軟なツリー構造の構築
別名
- オブジェクトツリー
- コンポジット構造
コンポジットデザインパターンの目的
オブジェクトをツリー構造に構成して、部分と全体の階層構造を表現します。コンポジットデザインパターンを使用すると、クライアントは個々のオブジェクトとオブジェクトの構成を均一に扱うことができます。
コンポジットパターンの詳細な解説と実世界の例
実世界の例
実世界の例として、複雑な組織構造を持つ企業を考えてみましょう。企業は様々な部署で構成されており、各部署にはさらに下位の部署や、最終的には個々の従業員が含まれます。コンポジットデザインパターンは、この構造を表現するために使用できます。各部署と従業員はツリー構造のノードとして扱われ、部署は他の部署や従業員を含むことができますが、従業員は子を持たないリーフノードです。これにより、企業は個々の従業員と部署全体を同じように扱うことで、総給与の計算や組織図の印刷などの操作を均一に実行できます。
平易な言葉で言うと
コンポジットデザインパターンを使用すると、クライアントは個々のオブジェクトとオブジェクトの構成を均一に扱うことができます。
Wikipediaによると
ソフトウェア工学において、コンポジットパターンは分割設計パターンです。コンポジットパターンは、オブジェクトのグループをオブジェクトの単一インスタンスと同じように扱う必要があることを記述します。コンポジットの目的は、オブジェクトをツリー構造に「構成」して、部分と全体の階層を表現することです。コンポジットパターンを実装することで、クライアントは個々のオブジェクトと構成を均一に扱うことができます。
Javaにおけるコンポジットパターンのプログラム例
すべての文は単語で構成され、単語はさらに文字で構成されています。これらのオブジェクトはすべて印刷可能であり、文は常にピリオドで終わり、単語は常に前にスペースがあるように、前後に何かを印刷できます。
ここでは、基底クラスのLetterComposite
と、印刷可能な異なるタイプLetter
、Word
、Sentence
があります。
public abstract class LetterComposite {
private final List<LetterComposite> children = new ArrayList<>();
public void add(LetterComposite letter) {
children.add(letter);
}
public int count() {
return children.size();
}
protected void printThisBefore() {
}
protected void printThisAfter() {
}
public void print() {
printThisBefore();
children.forEach(LetterComposite::print);
printThisAfter();
}
}
public class Letter extends LetterComposite {
private final char character;
public Letter(char c) {
this.character = c;
}
@Override
protected void printThisBefore() {
System.out.print(character);
}
}
public class Word extends LetterComposite {
public Word(List<Letter> letters) {
letters.forEach(this::add);
}
public Word(char... letters) {
for (char letter : letters) {
this.add(new Letter(letter));
}
}
@Override
protected void printThisBefore() {
System.out.print(" ");
}
}
public class Sentence extends LetterComposite {
public Sentence(List<Word> words) {
words.forEach(this::add);
}
@Override
protected void printThisAfter() {
System.out.print(".");
}
}
次に、メッセージを運ぶメッセンジャーがあります。
public class Messenger {
LetterComposite messageFromOrcs() {
var words = List.of(
new Word('W', 'h', 'e', 'r', 'e'),
new Word('t', 'h', 'e', 'r', 'e'),
new Word('i', 's'),
new Word('a'),
new Word('w', 'h', 'i', 'p'),
new Word('t', 'h', 'e', 'r', 'e'),
new Word('i', 's'),
new Word('a'),
new Word('w', 'a', 'y')
);
return new Sentence(words);
}
LetterComposite messageFromElves() {
var words = List.of(
new Word('M', 'u', 'c', 'h'),
new Word('w', 'i', 'n', 'd'),
new Word('p', 'o', 'u', 'r', 's'),
new Word('f', 'r', 'o', 'm'),
new Word('y', 'o', 'u', 'r'),
new Word('m', 'o', 'u', 't', 'h')
);
return new Sentence(words);
}
}
そして、このように使用できます。
public static void main(String[] args) {
var messenger = new Messenger();
LOGGER.info("Message from the orcs: ");
messenger.messageFromOrcs().print();
LOGGER.info("Message from the elves: ");
messenger.messageFromElves().print();
}
コンソール出力
20:43:54.801 [main] INFO com.iluwatar.composite.App -- Message from the orcs:
Where there is a whip there is a way.
20:43:54.803 [main] INFO com.iluwatar.composite.App -- Message from the elves:
Much wind pours from your mouth.
Javaでコンポジットパターンを使用するべきとき
以下の場合にコンポジットパターンを使用します。
- オブジェクトの部分と全体の階層を表現したい場合。
- クライアントがオブジェクトの構成と個々のオブジェクトの違いを無視できるようにしたい場合。クライアントは、コンポジット構造内のすべてのオブジェクトを均一に扱います。
Javaにおけるコンポジットパターンの実世界での応用例
- コンポーネントが他のコンポーネント(ボタン、ラベル、その他のパネルを含むパネルなど)を含むことができるグラフィカルユーザーインターフェース。
- ディレクトリがファイルや他のディレクトリを含むことができるファイルシステム表現。
- 部署が下位部署や従業員を含むことができる組織構造。
- java.awt.Container と java.awt.Component
- Apache Wicket のコンポーネントツリー。 Component と MarkupContainer を参照してください。
コンポジットパターンの利点とトレードオフ
利点
- クライアントコードを簡素化します。コンポジット構造と個々のオブジェクトを均一に扱うことができるためです。
- 新しい種類のコンポーネントを追加するのが簡単になります。既存のコードを変更する必要がないためです。
トレードオフ
- 設計が過度に一般的になる可能性があります。コンポジットのコンポーネントを制限することが難しい場合があります。
- コンポジット内のコンポーネントの型を制限するのが難しくなる可能性があります。
関連するJavaのデザインパターン
- フライウェイト:コンポジットは、複数のコンポジット間でコンポーネントインスタンスを共有するためにフライウェイトを使用できます。
- イテレーター:コンポジット構造を走査するために使用できます。
- ビジター:コンポジット構造に対して操作を適用できます。