Composite
Rubyによるデザインパターン生活、、4日目。
#しばらく飲み会続きでお休みしていました。
、、継続します。
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 12人 クリック: 193回
- この商品を含むブログ (58件) を見る
本日はCompositeパターンを取り上げます。
僕の中では、Compositeというと「集約」というイメージがあります。オブジェクト思考とかで。
Rubyによるデザインパターン本では「部分から全体を組み立てる」と紹介しています。
そんじゃ見ていきます。
本の中では、ケーキを作るというプロセスをツリー構造に分解しています。
詳細化したタスクをCompositeパターンで処理しよう!ってかんじ。
- MakeCake(ケーキを作る)
- Make Batter
- AddDryIngredients
- AddLiquids
- Mix
- Package Cake(ケーキを箱詰めにする)
- 省略
- Make Batter
みたいなかんじで、ケーキを作るための作業を詳細化しています。
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
以上、おしまい。
Observer
Rubyによるデザインパターン生活3日目。in 市ヶ谷。
今日はObserverパターンを取り上げます。
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 12人 クリック: 193回
- この商品を含むブログ (58件) を見る
Observerってのは「観測者」っていう意味が一番しっくりくるかな。
あるオブジェクトの状態変化を観測し、
変化が発生したことを知らせるのが目的です。
著者のラス・オルセンさんも言ってるけど、
Observerとなるクラスが観測者にあたるんだけど、
実際に観測者に変化を通知するのは、観測対象となるオブジェクト(Observable)なります。
#その辺でObserverパターンのネーミングは若干微妙な気もします。
そんな話はさておき、今回登場するクラスは以下のとおりです。
- Employee
- 従業員クラス。名前、役職、給料といった状態をもつ。状態が変化したら観測者(Observer)へ通知する。
- Payroll
- 給与明細クラス。従業員の給料の変化などを観測するObserverクラス。
- TaxMan
- 税金クラス。従業員の給料の変化などを観測するObserverクラス。
つづいてソースコードです。
まず、Employeeクラス。
class Employee attr_reader :name attr_accessor :title, :salary def initialize(name, title, salary) @name = name @title = title @salary = salary @observers = [] end def salary=(new_salary) @salary = new_salary notify_observers end def notify_observers @observers.each do |observer| observer.update(self) end end def add_observer(observer) @observers << observer end def delete_observer(observer) @observers.delete(observer) end end
インスタンス変数@observersがポイント。
ここに観測者(Observer)となるオブジェクトを保持します。
@observersへの追加にはadd_observerメソッドを使います。
状態であるsalaryが変化した場合、notify_observersメソッドが呼び出されるので、
結果として観測者クラス(Observer)のupdateメソッドが呼び出されます。
つづいて、観測者(Observer)となるPayrollクラスです。
class Payroll def update(changed_employee) puts "#{changed_employee.name}のために小切手を切ります。" puts "給料は#{changed_employee.salary}です。" end end
変更通知用のupdateメソッドを持っています。
引数には観測対象であるEmployeeオブジェクトを受け取ります。
つづいてTaxManクラス。こちらもObserverクラスになります。
class TaxMan def update(changed_employee) puts "#{changed_employee.name}に新しい税金の請求書を送ります。" end end
Payrollクラスと同じくupdateメソッドを実装しています。
実行用のプログラムです。
fred = Employee.new("Fred", "Crane Operator", 30000.0) payroll = Payroll.new fred.add_observer(payroll) taxman = TaxMan.new fred.add_observer(taxman) fred.salary = 35000.0
そんで、実行結果です。
Fredのために小切手を切ります。 給料は35000.0です。 Fredに新しい税金の請求書を送ります。
以上が一般的なObserverパターンの流れです。
Payroll、TaxMan以外の観測者(Observer)クラスが追加された場合も、同様に処理できます。
ここまでのコードでまずいところは、
Employeeクラスの中に観測対象者(Observable)として状態や振る舞いが実装されてしまっているところ。
Employeeクラスが読みにくいものになっています。
なんで、コードを分離してあげます。
観測対象者(Observable)に該当するコードを抽出します。
今回はSubject(観測対象)という意味あいのクラスとして抽出します。
が、Subjectクラスをスーパークラスとして抽出してしまうと、
継承関係の制約(単一継承しかできない)を受けるためよろしくありません。
なんで、Rubyに用意されているモジュール機能を使います。
Rubyは複数のモジュールをインクルード可能です。
モジュールはJavaのインタフェースに近いような?気がします。
#実装つきのインタフェースってかんじ?
Subjectモジュール。
module Subject def initialize @observers = [] end def add_observer(observer) @observers << observer end def delete_observer(observer) @observers.delete(observer) end def notify_observers @observers.each do |observer| observer.update(self) end end end
Employeeクラス。Subjectモジュールをインクルードしています。
class Employee include Subject attr_reader :name attr_accessor :title, :salary def initialize(name, title, salary) super() @name = name; @title = title @salary = salary end def salary=(new_saraly) @salary = new_saraly notify_observers end end
以上がObserverパターンのRubyっぽいモジュールを使った方法になります。
ちなみにRubyのライブラリの中にもObservableモジュールが用意されています。
話が少し変わりますが、
RubyでObserverパターンを実現する場合、
コードブロックをObserverクラスに見立てて処理することもできます。
Subjectクラスをこんなかんじに変更します。
module Subject def initialize @observers = [] end def add_observer(&observer) @observers << observer end def delete_observer(observer) @observers.delete(observer) end def notify_observers @observers.each do |observer| observer.call(self) end end end
add_observer(&observer)メソッドの引数がコードブロックの参照になっているのと、
notify_observersメソッドの中でObserverとなるコードブロックを実行しています。
呼び出しもとのプログラムはこんなかんじになります。
require "employee" fred = Employee.new("Fred", "Crane Operator", 30000.0) fred.add_observer do |changed_employee| puts "#{changed_employee.name}のために小切手を切ります。" puts "給料は#{changed_employee.salary}です。" end fred.salary = 35000.0
fred.add_observerの引数にコードブロックを渡しています。
これがObserverとして機能します。
実行結果はさきほどと同じ。
Fredのために小切手を切ります。 給料は35000.0です。
以上で、Observerパターンおしまい。
ちなみに、コードを読んで、ブログにまとめると大体2時間くらいかかります。
それが勉強になっていると信じてあと10コくらいのパターンの紹介を続けます。
あと、クラス図を簡単に書けるツール(Webで使える)あればいいなー。と。
おしまい。