Rubyデザインパターン学習のために、自分なりに読書の結果をまとめていくことに決めました。第4日目はCompositeです。(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=morizyun00-22)
4日目 Composite
4日目はCompositeパターンです。
このパターンの特徴は、ツリーや階層構造を作るときに便利なパターンになります。
階層、ツリー構造のオブジェクトをつくりたいとき、そのツリーを利用するコードが一つのオブジェクトを扱っているのか、ごちゃごちゃした枝全体を扱っているのか考えさせたくないときに便利です。
具体的なコードに移ります。ケーキ屋さんがケーキを作る工程を表現するプログラムを例にあげます。
構成要素として必要な物は以下の3点です
- コンポーネント(基底クラス
- 子クラス(小麦粉の計量、卵の投入などそれ以上機能を分離できない最も小さな要素
- コンポジット(子クラスをまとめあげるもの
Composite サンプルコード
class Task
attr_reader :name
def initialize(name)
@name = name
end
def get_time_required
0.0
end
end
class AddDryIngredientsTask < 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
end
end
class MakeBatterTask < Task
def initialize
super('Make batter')
@sub_tasks = []
add_sub_task( AddDryIngredientsTask.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
batter = MakeBatterTask.new
batter.get_time_required # => 4.0
MakeBatterTaskでは、子クラスを管理するためのメソッドを定義してあります。具体的には以下のふたつです。
add_sub_task
remove_sub_task
このふたつのメソッドで@sub_tasks
配列に必要な子タスクを出し入れします。
add_sub_task
で追加された要素は、オーバーライドされたget_time_required
で各々のクラスで定義された作業時間を合計し、出力します。
新しい子クラスを作ったときは、@sub_tasks
に入れて扱う事で、等価に増やすことができます。
class MakeBatterTask < CompositeTask
def initialize
super('Make batter')
add_sub_task( AddDryIngredientsTask.new )
add_sub_task( AddLiquidsTask.new )
add_sub_task( MixTask.new )
end
end
class MakeCakeTask < CompositeTask
def initialize
super('Make cake')
add_sub_task( MakeBatterTask.new )
add_sub_task( FilePanTask.new )
add_sub_task( BakeTask.new )
add_sub_task( FrostTask.new )
add_sub_task( LickSpoonTask.new )
end
end
そして、最後はこういった形でコンポジットが子クラスをまとめあげることができるように、コンポジットがコンポジットをまとめあげることができます。
再帰的な実装にしてあるので、階層がどれだけ深くなっても対応できるようになっています。
def get_time_required
time = 0.0
@sub_tasks.each {|task| time += task.get_time_required }
time
end
まとめ
作業を定義する具体的なクラスも、それをまとめあげて管理するクラスも似たようなメソッドを持っており、扱う側からどちらも等価なものとして扱いたいときに有効です。
例えると、ディレクトリとファイルの関係のように、親ディレクトリを削除したときに所属するディレクトリもファイルも削除する、といったシステムにも向いているかもしれません。
継承ベースであるTemplate Methodの、再帰的なバージョンとも言えるかもしれませんね。