デザインパターン学習メモ:「Composite」


このパターンの目的

GoF本より引用する。

部分―全体階層を表現するために、オブジェクトを木構造に組み立てる。Compositeパターンにより、クライアントは、個々のオブジェクトとオブジェクトを合成したものを一様に扱うことができるようになる。(P.175)

ここでいう木構造は、Linuxのファイルとディレクトリを想像するとイメージしやすいかもしれない。

Compositeパターンに当てはめると、ファイルとディレクトリは共通のインタフェースを持つことになる。クライアントはそれらを「同じもの」として扱い(ポリモーフィズム)、同じ方法(メソッド)で操作する。

実装例

Item.java
// 共通のインタフェース
// Component役
public abstract class Item {

    public abstract String getName();

    public abstract int getPrice();
}
Food.java
// 単品メニューを表す
// Leaf役
public class Food extends Item {
    private String name;
    private int price;

    public Food(String name, int price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public int getPrice() {
        return price;
    }
}
SetMenu.java
// セットメニューを表す
// Composite役
public class SetMenu extends Item {
    private String name;
    private List<Item> items;

    public SetMenu(String name, List<Item> item) {
        this.name = name;
        this.items = item;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public int getPrice() {
        Iterator<Item> iterator = getIterator();
        int price = 0;
        while (iterator.hasNext()) {
            Item item = iterator.next();
            price += item.getPrice();
        }
        return price;
    }

    private Iterator<Item> getIterator() {
        return items.iterator();
    }
}
Client
// Mainクラス
// Client役
public class Client {
    public static void main(String[] args) {
        // 単品の値段を取得
        Item food = new Food("Cheeseburger", 130);
        System.out.println(food.getPrice());

        // セットメニューの値段を取得
        Item[] items = { new Food("Cheeseburger", 130),
                new Food("Orange Juice", 100) };
        Item setMenu = new SetMenu("Cheeseburgar Set", Arrays.asList(items));
        System.out.println(setMenu.getPrice());
    }
}
結果
130
230

実装の解説

  • 単一のモノをあらわすインスタンス(Food.java)も集合をあらわすオブジェクト(SetMenu.java)も、同じgetPrice()で金額を取得できている
  • 上記では実装していないが、集合をあらわすほうのクラス(Composite役、SetMenu.java)に要素の追加と削除が必要になる場合、その処理をどこに実装すべきか問題になる。ケースバイケースで対応する。
    • 共通のインタフェースに実装する
      • 方法1:空の(何もしない)メソッドを用意して、「Leaf役(Food.java)」では何もしない動作とする
      • 方法2:デフォルトで例外を発生させるようにして、「Leaf役(Food.java)」で呼ばれた場合はエラーとする
    • 集合をあらわすクラス(Component役、SetMenu.java)に実装する
      • ポリモーフィズムを壊すおそれがある

参考文献

  • エリック ガンマ、ラルフ ジョンソン、リチャード ヘルム、ジョン プリシディース(1999)『オブジェクト指向における再利用のためのデザインパターン 改訂版』本位田 真一 / 吉田 和樹 監訳、ソフトバンククリエイティブ
  • 結城浩(2004)『Java言語で学ぶ デザインパターン入門』SBクリエイティブ