dorivenの日記

気がついたら社会人。気になる技術的なことについて少しずつ書いていけたらと思っております。

【デザインパターン】コンポジットパターン

普通に書くの忘れてました。
(´ぅω・`)ネムイ
さて、今日は【Composite】パターンについてです。

参考書



さらっと読める「OREILLY Head First デザインパターン」を参考にしながら書いています。
しばらくはこいつを参考にデザインパターンについて書いていくよ!

一覧



この記事は以下の様な流れで記述される。

  1. Compositeパターンって何ですか?
  2. Compositeパターンのメリット
  3. 具体的な例

Compositeパターンって何ですか?



前回触れた (【デザインパターン】イテレータパターン - datchの日記) にも関係しており、
構造的なデータを再帰的に表すためのデザインパターンです。

Wikipediaさんも参照。

Composite パターンを用いるとディレクトリとファイルなどのような、木構造を伴う再帰的なデータ構造を表すことができる。
Compositeパターンクラス図

Compositeパターンのメリット


  1. Wikiにも書いてあるが、「枝」と「葉」の部分を同様に扱える(つまり、子情報を持っている集合と、子情報の無い単一のデータを同様に扱える)

具体的な例



では、いつも通り参考書の例を出してみましょう。

前回のIteratorパターンにより、ArrayListやArrayなどの異なるコンテナにおいても同様にイテレーションを行う事が出来る様になり、ウェイトレスに聞けば店のメニューはすぐに分かるようになったぞ!
さて、ここでウェイトレスにパンケーキ店のメニュー、食堂のメニュー、カフェのメニューを渡すことでそれぞれのメニューを表示している。

さて、実際にソースコードを覗いてみよう。

public void printMenue(){
  Iterator pancakeIterator = pancakeHouseMenu.createIterator();
  Iterator dinerIterator = dinerMenu.createIterator();
  Iterator cafeIterator = cafeMenu.createIterator();

  printMenue(pancakeIterator);
  printMenue(dinerIterator);
  printMenue(cafeIterator);
}

これではメニューリストが増えてきては大変です!
そこで、それぞれのIteratorArrayListに格納して対処するようにしました。

public class Waitress{
  ArrayList menus;

  public Waitress(ArrayList menus){
    this.menus = menus;
  }

  public void printMenue(){
    Iterator menuIterator = menus.iterator();
    while(menuIterator.hasNext()){
      Menu menu = (Menu)menuIterator.next();
      printMenu(menu.createIterator());
    }
  }
  
  public void printMenu(Iterator iterator){
    // あとは同様にイテレーション
  }
}

さて、これで万事解決です!
と、思った矢先に以下の様な要件が追加されました。
「メニュー内にデザートのサブメニューを追加したい!」

これは困りました。
今まではメニューの品物だけで、サブメニューが入る予定がなかったからです。

さて、ここで威力を発揮するのが今回の「Compositeパターン」になります。
現在のデータ構造の状態は以下のようになっています。

  • 引用:

GOF デザインパターン一覧

先ほどまでは品物のみで、図で言えば「葉」に相当する物のみでした。
しかし、ここに来てサブメニューという「枝」が追加されたので、Compositeパターンを用います。

Compositeは以下の木構造を構築するために、以下のような命令を有します。

  1. add : 子の追加
  2. remove : 子の削除
  3. getChild : 子の取得

これを参考にして更にメニュー表示に必要な以下の関数も追加します。

  1. getname
  2. getDescription
  3. getPrice
  4. isVesitable
  5. print

さて、ではこの関数を保持するCompositeクラスを作成していきましょう。

public abstract class MenuComponent{
  // コンポジットパターンの処理
  public void add(MenuComponent menuComponent){
    throw new UnsupportedOperationException();
  }
  public void remove(MenuComponent menuComponent){
    throw new UnsupportedOperationException();
  }
  public MenuComponent getChild(int i){
    throw new UnsupportedOperationException();
  }

  // 独自拡張の処理
  public String getName(){
    throw new UnsupportedOperationException();
  }
  public String getDescription(){
    throw new UnsupportedOperationException();
  }
  public String getPrice(){
    throw new UnsupportedOperationException();
  }
  public String isVegitable(){
    throw new UnsupportedOperationException();
  }

  public void print(){
    throw new UnsupportedOperationException();
  }
}

さて、それではこのCompositeクラスを拡張していきましょう。
まずは「葉」に相当する部分を作っていきます。

public class MenuItem extends MenuComponent{

  String name;
  String description;
  boolean vegetarian;
  double price;

  public MenuItem(String name, String description, boolean vegetarian, double price){
    this.name = name;
    this.description = description;
    this.vegetarian = vegetarian;
    this.price = price;
  }

  // 独自拡張の処理は省略

  public void print(){
    // 品物情報の表示
  }
}

次は「枝」に相当する部分です。
Compositeパターンでこのようなクラスを「コンポジット」と呼びます。

public class Menu extends MenuComponent{
  ArrayList menuComponents = new ArrayList();
  String name;
  String description;

  public Menu(String name, String description){
    this.name = name;
    this.description = description;
  }

  // コンポジットパターンの処理
  public void add(MenuComponent menuComponent){
    menuComponents.add(menuComponent);
  }
  public void remove(MenuComponent menuComponent){
    menuComponents.remove(menuComponent);
  }
  public MenuComponent getChild(int i){
    retur (MenuComponent)menuComponents.get(i);
  }

  // 独自拡張の処理は省略

  public void print(){
    // メニュー情報の表示

    // !!重要!!
    Iterator iterator = menuComponents.iterator();
    while(iterator.hasNext()){
      MenuComponet menuComponent = (MenuComponent)iterator.next();
      menuComponent.print();
    }
  }
}

これがCompositeパターンです!
Menu部分のprint()では、それがMenuなのかMenuItemなのかという型の違いを吸収し、全てのメニュー一覧を表示することが出来るようになりました!
再帰幅優先探索に親しみのある人は理解がし易いと思います。
これで複雑なデータ構造を持つ情報も、シンプルに取り扱うことが出来るようになりました。

最後に



非常に眠いです。