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


以上、おしまい。

Observer

Rubyによるデザインパターン生活3日目。in 市ヶ谷。
今日はObserverパターンを取り上げます。

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

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


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で使える)あればいいなー。と。


おしまい。