デザインパターン入門(Composite)


この記事ではCompositeについてまとめます。
wikipediaによると「ディレクトリとファイルなどのような、木構造を伴う再帰的なデータ構造を表すことができる」とあります。
参考:Compositeパターン

オブジェクト思考言語であれば、配列・スタック・リスト・ハッシュなどコレクションにオブジェクトを詰め込んで、順番に処理する形を良く見るかと存じます。Compositeパターンでは「単-多」と言うようにオブジェクトがツリー状になった構造(子要素を保持するオブジェクト、子要素を持たないオブジェクトが混在した構造)であっても個別オブジェクトの差異を無視して同じ処理を適応します。

構成要素の見取り図
コンポジットノード
 |-リーフノード
 |-リーフノード
 |-リーフノード

主な登場人物

no 名前 役割
1 コンポーネント コンポジット、リーフなどノードの1単位をこのように呼ぶ。ノードに実装するインターフェース
2 コンポジットノード 子要素を保持するオブジェクト
3 リーフノード 子要素を持たないオブジェクト

パターンを実装する
ノードを階層のように組み合わせることで、以下のように入り組んだ構成を表現します。架空の食堂メニュー表で考えます。

食堂メニュー
メニュー
 |-ご飯
 |  |-定食A ¥1,000
 |  |-定食B ¥1,200
 |
 |-麺
 |  |-ラーメン ¥900
 |  |-うどん  ¥800
 |
 |-飲み物
    |-コーラ ¥250
    |-お茶  ¥300
MenuCompnent.java
public abstract class MenuComponent {
    public void add(MenuComponent mc) {
        throw new UnsupportedOperationException();
    }
    public void remove(MenuComponent mc) {
        throw new UnsupportedOperationException();
    }
    public MenuComponent getChild(Integer i) {
        throw new UnsupportedOperationException();
    }

    abstract public String getName();
    abstract public String getDescription();
    abstract public void print();
}
Menu.java
import java.util.ArrayList;
import java.util.Iterator;

public class Menu extends MenuComponent {
    ArrayList<MenuComponent> mcs = new ArrayList<>();
    String name;
    String description;

    public Menu(String name, String description) {
        this.name = name;
        this.description = description;
    }
    public void add(MenuComponent mc) { mcs.add(mc); }
    public void remove(MenuComponent mc) { mcs.remove(mc); }
    public MenuComponent getChild(Integer i) { return mcs.get(i); }
    public String getName() { return name; }
    public String getDescription() { return description; }
    public void print() {
        System.out.print("\n" + getName());
        System.out.println("," + getDescription());
        System.out.println("--------");

        Iterator iterator = mcs.iterator();
        while(iterator.hasNext()) {
            MenuComponent mc = (MenuComponent) iterator.next();
            mc.print();
        }
    }
}
MenuItem.java
public class MenuItem extends MenuComponent {
    public String name;
    public String description;
    public Boolean coffee;
    public Integer price;

    public MenuItem(String name, String description, Boolean coffee, Integer price) {
        this.name = name;
        this.description = description;
        this.coffee = coffee;
        this.price = price;
    }
    public String getName() { return name; }
    public String getDescription() { return description; }
    public Integer getPrice() { return price; }
    public Boolean isCoffee() { return coffee; }
    public void print() {
        System.out.print(" " + getName());
        if (isCoffee()) System.out.print("(コーヒー付き)");
        System.out.println("," + getPrice());
        System.out.println(" -- " + getDescription());
    }
}
Main.java
public class Main {
    public static void main(String args[]) {
         MenuComponent rice = new Menu("ご飯", "定食メニュー");
         MenuComponent noodles = new Menu("麺", "麺類メニュー");
         MenuComponent drink = new Menu("飲み物", "飲み物メニュー");
         MenuComponent all = new Menu("メニュー", "今日はこちら");
         all.add(rice);
         all.add(noodles);
         all.add(drink);
         rice.add(new MenuItem("定食A", "お得な定食セットA", true, 1000));
         rice.add(new MenuItem("定食B", "お得な定食セットB", true, 1200));
         noodles.add(new MenuItem("ラーメン", "自家製ラーメン", false, 900));
         noodles.add(new MenuItem("うどん", "本格うどん", false, 800));
         drink.add(new MenuItem("コーラ", "赤いパッケージ", false, 250));
         drink.add(new MenuItem("お茶", "どこにでもあるお茶", false, 300));
         all.print();
    }
}
結果
$java Main
メニュー,今日はこちら
--------
ご飯,定食メニュー
--------
 定食A(コーヒー付き),1000
 -- お得な定食セットA
 定食B(コーヒー付き),1200
 -- お得な定食セットB

麺,麺類メニュー
--------
 ラーメン,900
 -- 自家製ラーメン
 うどん,800
 -- 本格うどん

飲み物,飲み物メニュー
--------
 コーラ,250
 -- 赤いパッケージ
 お茶,300
 -- どこにでもあるお茶

パターンを適用しているのは、Menu.javaのprint()です。オブジェクトの構成としては、Menuクラスでカテゴリを定義して、MenuItemクラスでメニューの詳細(メニュー、詳細、値段、コーヒー有無)を管理しています。MenuクラスのListにMenuオブジェクト、MenuItemオブジェクトを保管しておき、print()がデータを出力する際、Menuオブジェクト・MenuItemオブジェクトの有無、数に関わらず、それぞれに出力処理を実施します。

参考:Head Firstデザインパターン ―頭とからだで覚えるデザインパターン