読者です 読者をやめる 読者になる 読者になる

Murayama blog.

プログラミングと、その次の話

Composite

Rubyによるデザインパターン生活、、4日目。
#しばらく飲み会続きでお休みしていました。



、、継続します。



Rubyによるデザインパターン

Rubyによるデザインパターン


本日はCompositeパターンを取り上げます。
僕の中では、Compositeというと「集約」というイメージがあります。オブジェクト思考とかで。
Rubyによるデザインパターン本では「部分から全体を組み立てる」と紹介しています。



そんじゃ見ていきます。
本の中では、ケーキを作るというプロセスをツリー構造に分解しています。
詳細化したタスクをCompositeパターンで処理しよう!ってかんじ。

  • MakeCake(ケーキを作る)
    • Make Batter
      • AddDryIngredients
      • AddLiquids
      • Mix
    • Package Cake(ケーキを箱詰めにする)
      • 省略

みたいなかんじで、ケーキを作るための作業を詳細化しています。


Compositeパターンでは3つの部品を使います。

  • すべてのオブジェクトの基底となるクラス(Component)
  • 葉(Leaf)となるクラス
  • 小要素をもつ複合的なクラス(Composite)

今回登場するクラス。

  • Task
    • 基底となるクラス(Component)
  • AddDryIngredientsTask
    • 単体の作業を表すクラス(Leaf
  • AddLiquidsTask
    • 単体の作業を表すクラス(Leaf
  • MixTask
    • 単体の作業を表すクラス(Leaf
  • MakeBatterTask
    • 複数の作業によって構成されるクラス(Composite)


それでは、コードを見てみます。
まずは、Taskクラスです。

class Task
  attr_accessor :name
  def initialize(name)
    @name = name;
  end
  
  def get_time_required
    0.0
  end
end

属性nameとget_time_requiredメソッドを定義しています。
get_time_requiredメソッドは、作業に必要な時間を返します。
Taskクラスは抽象的なクラスのため、作業時間は0を返しています。
#結果としてサブクラスでオーバーライドすることになります。


つづいて、AddDryIngredientsTaskです。

class AddDryIngredientsTask < Task
  def initialize
    super('Add dry ingredients')
  end
  
  def get_time_required
    1.0
  end
end

Taskクラスを継承しています。get_time_requiredメソッドをオーバーライドしています。
AddDryIngredientsTaskクラスはCompositeパターンにおけるLeafにあたるクラスです。
そのため小要素は持たず、自身の作業だけを遂行します。

次に紹介するMixTask、AddLiquidsTaskクラスも同様のLeafにあたるクラスです。

class MixTask < Task
  def initialize
    super('Mix that batter up!')
  end

  def get_time_required
    3.0
  end
end
class AddLiquidsTask < Task
  def initialize
    super('Add Liquids task')
  end
  
  def get_time_required
    5.0
  end
end


つづいて、AddDryIngredientsTask、MixTask、AddLiquidsTaskによって構成されるMakeBatterTaskクラスです。

class MakeBatterTask < Task
  def initialize
    super('Make batter')
    @sub_tasks = []
    add_sub_task(AddDryIngredientsTask.new)
    add_sub_task(AddLiquidsTask.new)    
    add_sub_task(MixTask.new)
  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

MakeBatterTaskクラスはCompositeパターンにおけるCompositeにあたるクラスです。
自身は小要素となるAddDryIngredientsTask、MixTask、AddLiquidsTaskオブジェクトを保持します。
作業時間を求めるget_time_requiredメソッドでは、各小要素に対して作業時間を問い合わせ、合計した結果を返します。


最後に呼び出しもととなるプログラムです。

task = MakeBatterTask.new

puts task.name
puts task.get_time_required


実行結果です。

Make batter
9.0


と、こんなかんじです。


あとは、
MakeBatterTaskクラスからCompositeにあたる部分をCompositeTaskクラスとして切り出すこともできます。

class CompositeTask < Task
  def initialize(name)
    super(name)
    @sub_tasks = []
  end

  def add_sub_task(task)
    @sub_tasks << task
    task.parent_task = self
  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


するとMakeBatterTaskはシンプルになります。

class MakeBatterTask < CompositeTask
  def initialize
    super('Make batter')
    add_sub_task(AddDryIngredientsTask.new)
    add_sub_task(MixTask.new)
    add_sub_task(AddLiquidsTask.new)    
  end
end


また、CompositeTaskには配列を操作するような演算子メソッドを追加すると便利です。
<<メソッドや、[]メソッドなど。

class CompositeTask < Task
  def initialize(name)
    super(name)
    @sub_tasks = []
  end

  def add_sub_task(task)
    @sub_tasks << task
    task.parent_task = self
  end

  def <<(task)
    add_sub_task(task)
  end
  
  def remove_sub_task(task)
    @sub_tasks.delete(task)
  end
  
  def [](index)
    @sub_tasks[index]
  end
  
  def []=(index, new_value)
    @sub_tasks[index] = new_value
  end

  def total_number_basic_tasks
    total = 0
    @sub_tasks.each { |task| total += task.total_number_basic_tasks }
    total
  end

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


以上、おしまい。