デザインパターン入門 | Composite(コンポジット)パターン
「Composite(コンポジット)」パターンは、「木構造(Tree Structure)」を持つデータに「再帰的な処理」を行うことができるデザインパターンです。
世の中に「木構造」を持つ事例はたくさん見つかりますが、今回は「会社」をテーマに「Composite(コンポジット)」パターンについて説明をしていきたいと思います。
「Composite(コンポジット)」パターンの仕組み
「Composite(コンポジット)」パターンの説明で利用する「会社」を「木構造」で表すと下図のようになります。

親会社の下に子会社があり、さらに子会社の下には孫会社があるという「木構造」になっています。
会社は「部署」を持つことができるため、今回の事例では、子会社・孫会社に「部署」を持たせることにします。

デザインパターンの名前に付いている「Composite(コンポジット)」という英単語には「複合」という意味があります。
「Composite(コンポジット)」パターンの特徴は「複合されたインスタンス」を同一として扱うということにあります。
今回登場している「会社」と「部署」を複合して扱うということなのですが、この時点ではまだよくわかりませんよね・・・
詳細を説明する前に、まず下図のクラス関係を見ていただきたいと思います。

「会社」を表す「Company」クラスと、「部署」を表す「Department」クラスは「Component」抽象クラスを継承しています。
この関係が「Composite(コンポジット)」パターンの理解に大切な部分ですので、覚えておきましょう。
「Component」クラスは下記のようになります。
package SampleComposite;
public abstract class Component {
public abstract String getName();
public abstract int getEarnings();
}
それぞれのメソッドは、
- getName
- 「会社名・部署名」を取得
- getEarnings
- 「会社・部署」の売上高を取得
という役割を持っていますが、具体的な実装は継承先の子クラスで行います。
「会社」を表す「Company」クラスは下記のようになります。
package SampleComposite;
import java.util.ArrayList;
import java.util.Iterator;
public class Company extends Component{
// 会社名
private String name;
// 子会社または部署を保存
private ArrayList components = new ArrayList();
// コンストラクタ
public Company(String name) {
super();
this.name = name;
}
// 子会社または部署を追加
public Component add(Component component) {
components.add(component);
return this;
}
// 会社名を取得
@Override
public String getName() {
return this.name;
}
// 総売上高を取得
@Override
public int getEarnings() {
int earnings = 0;
Iterator it = components.iterator();
while ( it.hasNext() ) {
Component component = (Component)it.next();
earnings += component.getEarnings();
}
return earnings;
}
}
そして、「部署」を表す「Department」クラスは下記のようになります。
package SampleComposite;
public class Department extends Component{
// 部署名
private String name;
// 売上高
private int earnings;
// コンストラクタ
public Department(String name, int earnings) {
super();
this.name = name;
this.earnings = earnings;
}
// 部署名を取得
@Override
public String getName() {
return this.name;
}
// 部署の売上高を取得
@Override
public int getEarnings() {
return this.earnings;
}
}

「main」メソッドは下記のようになります。
package SampleComposite;
public class Main {
public static void main(String[] args) {
// 親会社の作成
Company rootCompany = new Company("親会社");
// 子会社の作成
Company subsidiaryA = new Company("子会社A");
Company subsidiaryB = new Company("子会社B");
//孫会社の作成
Company sub_subsidiaryA = new Company("孫会社A");
Company sub_subsidiaryB = new Company("孫会社B");
Company sub_subsidiaryC = new Company("孫会社C");
Company sub_subsidiaryD = new Company("孫会社D");
// 親会社に子会社を登録
rootCompany.add(subsidiaryA);
rootCompany.add(subsidiaryB);
// 子会社Aに孫会社を登録
subsidiaryA.add(sub_subsidiaryA);
subsidiaryA.add(sub_subsidiaryB);
// 子会社Bに孫会社を登録
subsidiaryB.add(sub_subsidiaryC);
subsidiaryB.add(sub_subsidiaryD);
// 子会社Aの部署を作成
subsidiaryA.add(new Department("住宅販売事業部",3000));
// 孫会社Aの部署を作成
sub_subsidiaryA.add(new Department("住宅資材生産事業部",900));
// 孫会社Bの部署を作成
sub_subsidiaryB.add(new Department("不動産管理事業部",200));
// 子会社Bの部署を作成
subsidiaryB.add(new Department("家電販売事業部",2000));
// 孫会社Cの部署を作成
sub_subsidiaryC.add(new Department("家電生産事業部",500));
// 孫会社Dの部署を作成
sub_subsidiaryD.add(new Department("リペア事業部",200));
// グループ全体の総売上高を取得
System.out.println("グループ全体の総売上高" + rootCompany.getEarnings() + "億円");
// 子会社Aの総売上高を取得
System.out.println("子会社Aの総売上高" + subsidiaryA.getEarnings() + "億円");
// 子会社Bの総売上高を取得
System.out.println("子会社B"
+ "の総売上高" + subsidiaryB.getEarnings() + "億円");
}
}
出力結果は次のようになります。
グループ全体の総売上高6800億円 子会社Aの総売上高4100億円 子会社Bの総売上高2700億円
「グループ全体」「子会社A」「子会社B」のそれぞれに対して「getEarnings」メソッドを実行していますが、「子会社・部署」が存在する場合は再帰的に下層の「売上高」を取得して合計を計算しています。
「Company」クラスも「Department」クラスも「Component」クラスを継承しているため、「getEarnings」メソッドを実装していることが保証されています。
「Composite(コンポジット)」パターンを利用することで、このように「子会社」も「部署」も同一と捉えて、「再帰的」に処理ができるようになります。
子会社をさらに追加した時も、そのほかの処理は変更せずに「グループ全体の総売上高」を取得することができます。
例えば、孫会社Dに「半導体製造事業部(売上高:1500億円)」を追加すると、
package SampleComposite;
public class Main {
public static void main(String[] args) {
// 親会社の作成
Company rootCompany = new Company("親会社");
// 子会社の作成
Company subsidiaryA = new Company("子会社A");
Company subsidiaryB = new Company("子会社B");
//孫会社の作成
Company sub_subsidiaryA = new Company("孫会社A");
Company sub_subsidiaryB = new Company("孫会社B");
Company sub_subsidiaryC = new Company("孫会社C");
Company sub_subsidiaryD = new Company("孫会社D");
// 親会社に子会社を登録
rootCompany.add(subsidiaryA);
rootCompany.add(subsidiaryB);
// 子会社Aに孫会社を登録
subsidiaryA.add(sub_subsidiaryA);
subsidiaryA.add(sub_subsidiaryB);
// 子会社Bに孫会社を登録
subsidiaryB.add(sub_subsidiaryC);
subsidiaryB.add(sub_subsidiaryD);
// 子会社Aの部署を作成
subsidiaryA.add(new Department("住宅販売事業部",3000));
// 孫会社Aの部署を作成
sub_subsidiaryA.add(new Department("住宅資材生産事業部",900));
// 孫会社Bの部署を作成
sub_subsidiaryB.add(new Department("不動産管理事業部",200));
// 子会社Bの部署を作成
subsidiaryB.add(new Department("家電販売事業部",2000));
// 孫会社Cの部署を作成
sub_subsidiaryC.add(new Department("家電生産事業部",500));
// 孫会社Dの部署を作成
sub_subsidiaryD.add(new Department("リペア事業部",200));
System.out.println("【半導体製造事業部追加前】");
// グループ全体の総売上高を取得
System.out.println("グループ全体の総売上高" + rootCompany.getEarnings() + "億円");
// 子会社Aの総売上高を取得
System.out.println("子会社Aの総売上高" + subsidiaryA.getEarnings() + "億円");
// 子会社Bの総売上高を取得
System.out.println("子会社B"
+ "の総売上高" + subsidiaryB.getEarnings() + "億円");
// 孫会社Dの部署を作成
sub_subsidiaryD.add(new Department("半導体製造事業部",1500));
System.out.println("");
System.out.println("【半導体製造事業部追加後】");
// グループ全体の総売上高を取得
System.out.println("グループ全体の総売上高" + rootCompany.getEarnings() + "億円");
// 子会社Aの総売上高を取得
System.out.println("子会社Aの総売上高" + subsidiaryA.getEarnings() + "億円");
// 子会社Bの総売上高を取得
System.out.println("子会社B"
+ "の総売上高" + subsidiaryB.getEarnings() + "億円");
}
}
のようになります。
出力結果は、次のようになります。
【半導体製造事業部追加前】 グループ全体の総売上高6800億円 子会社Aの総売上高4100億円 子会社Bの総売上高2700億円 【半導体製造事業部追加後】 グループ全体の総売上高8300億円 子会社Aの総売上高4100億円 子会社Bの総売上高4200億円
「総売上高」の取得処理を変更しなくても、追加した部署の売上高が加算されていることがわかるかと思います。
このように「木構造」を持つデータが追加されても、元の処理は全く変更せずに処理を行うことができます。
「木構造」を持つデータを扱う際は、「Composite(コンポジット)」パターンが使えないかを意識しながら開発に取り組めるようになっていきましょう。