Rubyによるデザインパターン【Composite】 -世界は再帰的（部分は全体、全体は部分）-




# 概要

[Rubyによるデザインパターン](http://www.amazon.co.jp/gp/product/4894712857/ref=as_li_qf_sp_asin_tl?ie=UTF8&camp=247&creative=1211&creativeASIN=4894712857&linkCode=as2&tag=kidachi-22)第6章。
Composite Pattern。

[Rubyによるデザインパターン5原則](http://qiita.com/kidachi_/items/4b63de9ad5a97726c50c)に則って理解する。


# どんなパターンか

- あるものが同じような下位のもので作られているという考え方
- 大きなオブジェクトが小さな子オブジェクトから構成されていて、その子オブジェクトもさらに小さな孫オブジェクトでできている


- 階層構造やツリー構造のオブジェクトを作りたい時に利用できる。


# 登場人物

### コンポーネント（Component）

すべてのオブジェクトの共通インターフェイス。もしくは基底クラス。

基本的なオブジェクトや上位のオブジェクトいずれも、必ず共通して共通して持っているもの。

例）ケーキ作成手順を例にとると、タスクの所要時間

### 葉（Leaf）

プロセスの単純な構成要素で、１つ以上必要。

例）小麦粉の計量や卵の投入など単純タスク


### コンポジット（Composite）

コンポーネントの一部だが、サブコンポーネントから作られる、より上位のオブジェクト。

いくつかの小タスクから構成される複合的なタスク。

例）生地の作成など、いくつかの子タスクから構成される複合的なタスク


# 実例


## コンポーネントクラス

全てのタスクに共通する、所要時間取得用の抽象的なget_time_requiredメソッド。

```ruby
class Task
  attr_reader :name

  def initialize(name)
    @name = name
  end

  def get_time_required
    0.0
  end
end
```

## 葉クラス

```ruby
class AddMilkTask < Task

  def initialize
    super('Add dry ingredients')
  end

  def get_time_required
    1.0  # 砂糖を加えるのに1分
  end
end

class MixTask < Task

  def initialize
    super('Mix that batter up!')
  end

  def get_time_required
    3.0  # 交ぜるのに3分
  end
end
```

## コンポジットクラス

葉クラス達からなる複合的作業のクラス

```ruby
class MakeBatterTask < Task

  def initialize
    super('Make batter')
    @sub_tasks = []
    add_sub_task(AddMilkTask.new)
    add_sub_task(MixTask.new)
    # add_sub_task(AddFreshCreamTask.new)
    # etc ..
  end

  def add_sub_task(task)
    @sub_tasks << task
  end

  def remove_sub_task(task)
    @sub_tasks.delete(task)
  end

  def get_time_required
    time=0.0
    @sub_tasks.each { |task| time += task.get_time_required }
    time
  end
end
```

## クラスの利用

```ruby
make_batter = MakeBatterTask.new
make_batter.get_time_required
=> 4.0
```

# コンポジットクラスの洗練

## コンポジットタスクが複数でてくることを見越して、コンポジット用基底クラスを作成

```ruby
class CompositeTask < Task

  def initialize(name)
    super(name)
    @sub_tasks = []
  end

  def add_sub_task(task)
    @sub_tasks << task
  end

  def remove_sub_task(task)
    @sub_tasks.delete(task)
  end

  def get_time_required
    time=0.0
    @sub_tasks.each { |task| time += task.get_time_required }
    time
  end
end
```

## コンポジットタスク1

バター作成用クラス

```ruby
class MakeBatterTask < CompositeTask

  def initialize
    super('Make batter')
    add_sub_task(AddMilkTask.new)
    add_sub_task(MixTask.new)
    # add_sub_task(AddFreshCreamTask.new)
    # etc ..
  end
end
```

## コンポジットタスク2

ケーキ作成用クラス

→コンポジットタスクであるバター作成クラスを部分として持つ、より上位の複合クラス。


```ruby
class MakeCakeTask < CompositeTask

  def initialize
    super('Make cake')
    add_sub_task(MakeBatterTask.new)
    # add_sub_task(FillPanTask.new)
    # add_sub_task(BakeTask.new)
    # etc ..
  end
end
```

## クラスの利用

```ruby
make_batter = MakeBatterTask.new
make_batter.get_time_required
=> 4.0

make_cake = MakeCakeTask.new
make_cake.get_time_required
=> 4.0  # MakeCakeTaskにちゃんとsub_taskを追加すればこの値が増える
```




# コンポジットクラスの洗練2

コンポジットオブジェクトは、コレクションとしての役割も持ち合わせている。
（MakeButterは、AddMilkTaskやMixTaskを持つ）

```ruby
class CompositeTask < Task

  def initialize(name)
    super(name)
    @sub_tasks = []
  end

  # Array的な要素の追加
  def <<(task)
    @sub_tasks << task
  end

  # Array的な値へのアクセス
  def [](index)
    @sub_tasks[index]
  end

  # Array的な値の代入
  def []=(index, new_value)
    @sub_tasks[index] = new_value
  end

  def remove_sub_task(task)
    @sub_tasks.delete(task)
  end

  def get_time_required
    time=0.0
    @sub_tasks.each { |task| time += task.get_time_required }
    time
  end
end
```

```ruby
class MakeBatterTask < CompositeTask

  def initialize
    super('Make batter')
    self << AddMilkTask.new
    self << MixTask.new
  end
end
```

```ruby
make_cake = MakeCakeTask.new
make_cake.get_time_required
=> 4.0

# Array的アクセス
make_cake[0]
=> #<MakeBatterTask:0x007fab9dcc9b60
 @name="Make batter",
 @sub_tasks=
  [#<AddMilkTask:0x007fab9dcc9ae8 @name="Add dry ingredients">,
   #<MixTask:0x007fab9dcc9a98 @name="Mix that batter up!">]>


# make_cakeのタスクをMakeBatterTaskからMixTaskに変更
make_cake[0] = MixTask.new
=> #<MixTask:0x007faba03c1fb0 @name="Mix that batter up!">

# MixTaskとなり所用時間が減った
make_cake.get_time_required
=> 3.0
```


# 部分から全体への参照

全体から部分への参照は、自身のクラスを見れば一目瞭然だが、その逆は現状難しい。

全体に対する参照を扱うコードを追加する。
追加先としては、共通インターフェイスであるコンポーネントクラスが適切。


```ruby
class Task
  attr_accessor :name, :parent

  def initialize(name)
    @name = name
    @parent = nil  # 全体（親）情報を保持する属性を追加
  end

  def get_time_required
    0.0
  end
end
```

全体（parent）の属性を設定するのは、上位概念であるコンポジットクラスが適切。

```ruby
class CompositeTask < Task

  def initialize(name)
    super(name)
    @sub_tasks = []
  end

  def <<(task)
    @sub_tasks << task
    task.parent = self  # 部分クラス（task）に全体（self）情報を追加
  end

  def remove_sub_task(task)
    @sub_tasks.delete(task)
    task.parent = nil  # 部分クラス（task）から全体情報を削除
  end

  def get_time_required
    time=0.0
    @sub_tasks.each { |task| time += task.get_time_required }
    time
  end
end
```

## 部分から全体への参照を試す

AddMilkTaskとMixTaskからなるMakeBatterTaskを定義

```ruby
class MakeBatterTask < CompositeTask

  def initialize
    super('Make batter')
    self << AddMilkTask.new
    self << MixTask.new
  end
end
```

MakeBatterTaskに追加するためのAddFreshCreamTaskを定義

```ruby
class AddFreshCreamTask < Task

  def initialize
    super('Add Fresh Cream')
  end

  def get_time_required
    5.0
  end
end
```

- make_batterタスク（全体）にadd_fresh_cream（部分）を追加
- add_fresh_creamに対する全体（親）を取得

```ruby
# make_batterを初期化
[12] pry(main)> make_batter = MakeBatterTask.new
[13] pry(main)> make_batter.get_time_required
=> 4.0

# make_batterタスク（全体）にadd_fresh_cream（部分）を追加
[15] pry(main)> add_fresh_cream = AddFreshCreamTask.new
[16] pry(main)> make_batter << add_fresh_cream
[17] pry(main)> make_batter.get_time_required
=> 9.0

# add_fresh_creamに対する全体（親）を取得
[18] pry(main)> add_fresh_cream.parent
=> #<MakeBatterTask:0x007fbd13ac6a98
 @name="Make batter",
 @parent=nil,
 @sub_tasks=
  [#<AddMilkTask:0x007fbd13ac69f8 @name="Add dry ingredients", @parent=#<MakeBatterTask:0x007fbd13ac6a98 ...>>,
   #<MixTask:0x007fbd13ac69a8 @name="Mix that batter up!", @parent=#<MakeBatterTask:0x007fbd13ac6a98 ...>>,
   #<AddFreshCreamTask:0x007fbd1352e388 @name="Add Fresh Cream", @parent=#<MakeBatterTask:0x007fbd13ac6a98 ...>>]>
```

# Compositeパターンの注意点

コンポジット（全体）から葉クラスの数を取得したい時

```ruby
class CompositeTask < task
  ~
  def total_number_basic_tasks
    @sub_tasks.length
  end
end
```

これは間違い。
→コンポジットの構成要素がまたコンポジットである可能性を無視してしまっている。

葉の数を全て数えるには、コンポジットクラスを再帰的に辿る必要がある。
以下2カ所に変更を加える。

```ruby
class Task
  ~
  # 全ての葉はtotal_number_basic_tasksを保持する。
  def total_number_basic_tasks
    1
  end
end
```

```ruby
class CompositeTask < Task
  ~
  # CompositeTaskに各タスクのtotal_number_basic_tasksを取得する本メソッドを追加することで、再帰的に（全ての葉クラスまで辿って）task数を取得する。
  def total_number_basic_tasks
    @sub_tasks.inject(0) {|sum, task| sum + task.total_number_basic_tasks}
  end
end
```

### total_number_basic_tasksの呼び出し

```ruby
make_batter.total_number_basic_tasks
=> 3
```

うまくいった。

# まとめ

- 一見複雑なオブジェクトたちを再帰的に整理し、変化に強い構造へ。
- ベーシックな考え方であり、他のパターンに紛れて登場することも多い。

# 以下へ続く

【Iterator】-君の子供たちに伝えたいのだけど-
http://qiita.com/kidachi_/items/afa4c6c29a6eb6be487a