Rubyによるデザインパターンまとめ①
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 13人 クリック: 220回
- この商品を含むブログ (66件) を見る
template method
利用する場面
あるインスタンスに行わせる処理の内容を、インスタンスの種類に応じて変えたい場合。
方法
- クラスHogeにメソッドbehaveを定義する。
- サブクラスsub1, sub2を定義し、それぞれでメソッドbehaveをオーバーライドする。
注意すべき点
- 継承ベースであるため、メソッドbehave以外にも全部親クラスのことを引き継いでしまう。
- サブクラスと振る舞いが対応しているため、サブクラスのインスタンスをつくった後は、そのインスタンスの振る舞いを容易に変更することができない。
class Vertebrate end class Mammal < Vertebrate def move puts :walk end end class Fish < Vertebrate def move puts :swim end end class Reptile < Vertebrate def move puts :crawl end end inu_tarou = Mammal.new inu_tarou.move # => walk saba_tarou = Fish.new saba_tarou.move # => swim tokage_tarou = Reptile.new tokage_tarou.move # => crawl
strategy
利用する場面
あるインスタンスに行わせる処理の内容を、インスタンスの種類に応じて変えたい場合。 (詳細な戦略を各strategyクラスに隠蔽する)
方法
- behaveを行うためのstrategyとしてクラスBehaviorをつくり、メソッドbehaveを定義する。
- BehaviorのサブクラスSubBehavior1, SubBahavior2をつくり、メソッドbehaveをオーバーライドする。
- SubBehaviorXだけで事足りるのであれば、基底クラスBehaviorはなくてもよい。(以下の例では名前空間でまとまりをもたせるためにモジュールのみ設けている。)
class Vertebrate attr_accessor :mover def initialize(mover) self.mover = mover end def move mover.move end end class Vertebrate attr_accessor :mover def initialize(mover) self.mover = mover end def move mover.move end end module Mover class Walker def move puts :walk end end class Swimmer def move puts :swim end end class Crawler def move puts :crawl end end end inu_tarou = Vertebrate.new(Mover::Walker.new) inu_tarou.move # => walk saba_tarou = Vertebrate.new(Mover::Swimmer.new) saba_tarou.move # => swim tokage_tarou = Vertebrate.new(Mover::Crawler.new) tokage_tarou.move # => crawl
Template Methodと違って、あるインスタンスについて容易に振る舞いを切り替えることができる。 (戦略は変更することができる!)
tokage_tarou.mover = Mover::Walker.new tokage_tarou.move # => walk
Strategyクラスとはつまるところ処理を記述したものに過ぎないので、Procオブジェクトに置き換えることができる。(クラスをつくる必要がない!)
class Vertebrate attr_accessor :mover def initialize(&mover) self.mover = mover end def move mover.call end end walker = lambda { puts :walk } swimmer = lambda { puts :swim } crawler = lambda { puts :crawl } saba_tarou = Vertebrate.new(&walker) saba_tarou.move # => walk inu_tarou = Vertebrate.new(&swimmer) inu_tarou.move # => swim tokage_tarou = Vertebrate.new(&crawler) tokage_tarou.move # => crawl
予めProcをつくらずとも、インスタンスを生成するときにその場限りのブロックを渡してもよい。
tori_tarou = Vertebrate.new { puts :fly } tori_tarou.move # => fly
command
利用する場面
strategyとの関係
やってることはざっくり同じだと思う。 同じ呼び出し方のできる複数の処理方法を(classやprocとして)用意して、contextとなるインスタンスに1つまたは複数の処理方法をもたせて、polymorphicに呼び出す。 1つのcontextに1つだけ持たせる処理方法をstrategyと呼び、1つのcontextにいくつも持たせる処理方法をcommandと呼ぶのではないだろうか。(同時に複数の戦略をもつことはできないからね。)
observer
利用する場面
あるオブジェクトで何かが起きたときに、他のオブジェクトに知らせたい(=他のオブジェクトの決まりきったメソッドを呼び出したい)とき。
方法
- 発信者のインスタンスに、@observersを持たせる。
- 通知すべきことが発生したら、発信者は
@observers.each { |observer| observer.update(self) }
などにより知らせる。
その他
- 他のオブジェクトに送りたいメッセージが決まりきったものでないならば、普通に書けばよいだけ。observerパターンは「お知らせするupdateメソッドを呼び出したいだけ」というような場合に用いる。
observer.update
の引数をselfにしてobserverの側で必要な情報を引き出すのをpull型、subject側で予め詳細なデータを用意して引数として渡すのをpush型と呼ぶ。- observerというパターン名ではあるが、実際に色々するのは、発信者=subjectの側であることに注意する。
- observerに関するもろもろの処理をSubjectクラスorモジュールとして切り分け、これを継承or includeしてもよい。
- Rubyにはobservableという組み込みメソッドがある。
- 「これまでsubjectが登録されたobserverにニュースを配信するという例えを使ってきましたが、実際のところ、あるオブジェクトが他のオブジェクトのメソッドを呼び出す、という話をしているに過ぎません。」
- observerもstrategy/commandも、あるオブジェクトが他のオブジェクトを呼び出す仕組みに過ぎない。呼び出されるオブジェクト自体が処理を表現しているならstrategy/command, 呼び出されるオブジェクトが単一の処理を表現するだけでなくより実体的なオブジェクトで、「observeする」とか「notifyされる」とかいう表現がしっくりくるならobserver、なんだと思う。
サンプル
class Parent def update(subject, action) puts "大変だ!#{subject.name}が#{action}している!" end end class Baby attr_reader :name def initialize(name, *observers) @name = name @observers = observers end def cry puts "mewl" @observers.each { |observer| observer.update(self, :cry) } end end tom = Parent.new mary = Parent.new Baby.new(:babee, tom, mary).cry # => mewl # => 大変だ!babeeがcryしている! # => 大変だ!babeeがcryしている!
composite
利用する場面
部分と全体を同じように扱いたいとき
方法
以下を定義する。
- component: 全てのノードの基底クラス
- leaf: 末端ノード(必要に応じて抽象クラスを定義する)
- composite: 末端以外のノード=枝を持つノード(必要に応じて抽象クラスを定義する)
サンプル
以下の構造をもつ組織をコードで表現する。
- Company
- SalesDep
- CorporateSalesDiv
- PrivateSalesDiv
- DevelopmentDep
- InfraDiv
- AppDiv
- GeneralAffairsDep
- FinancialDiv
- LegalDiv
- SalesDep
# (抽象)component class Group attr_accessor :super_group end # 抽象leaf class Div < Group attr_reader :member_count, :budget end # 具象leaf class CorporateSalesDiv < Div def initialize @member_count = 5 @budget = 280 end end class PrivateSalesDiv < Div def initialize @member_count = 4 @budget = 200 end end class InfraDiv < Div def initialize @member_count = 3 @budget = 360 end end class AppDiv < Div def initialize @member_count = 6 @budget = 250 end end class FinanceDiv < Div def initialize @member_count = 2 @budget = 120 end end class LegalDiv < Div def initialize @member_count = 1 @budget = 60 end end # 抽象composite class CompositeGroup < Group attr_reader :sub_groups def initialize @sub_groups = [] end def add_sub_groups(*sub_groups) sub_groups.each { |sub_group| sub_group.super_group = self} @sub_groups.push(*sub_groups) end def member_count @sub_groups.inject(0) {|sum, sub_group| sum += sub_group.member_count} end def budget @sub_groups.inject(0) {|sum, sub_group| sum += sub_group.budget} end end # 具象composite class Company < CompositeGroup def initialize super add_sub_groups(SalesDep.new, DevelopmentDep.new, GeneralAffairsDep.new) end end class SalesDep < CompositeGroup def initialize() super add_sub_groups(CorporateSalesDiv.new, PrivateSalesDiv.new) end end class DevelopmentDep < CompositeGroup def initialize super add_sub_groups(InfraDiv.new, AppDiv.new) end end class GeneralAffairsDep < CompositeGroup def initialize super add_sub_groups(FinanceDiv.new, LegalDiv.new) end end
上記により、以下が実現する。
> company = Company.new > pp company #<Company:0x007f85730c7060 @sub_groups= [#<SalesDep:0x007f85730c6f20 @sub_groups= [#<CorporateSalesDiv:0x007f85730c6d90 @budget=280, @member_count=5, @super_group=#<SalesDep:0x007f85730c6f20 ...>>, #<PrivateSalesDiv:0x007f85730c6ca0 @budget=200, @member_count=4, @super_group=#<SalesDep:0x007f85730c6f20 ...>>], @super_group=#<Company:0x007f85730c7060 ...>>, #<DevelopmentDep:0x007f85730c6bb0 @sub_groups= [#<InfraDiv:0x007f85730c6b10 @budget=360, @member_count=3, @super_group=#<DevelopmentDep:0x007f85730c6bb0 ...>>, #<AppDiv:0x007f85730c6ac0 @budget=250, @member_count=6, @super_group=#<DevelopmentDep:0x007f85730c6bb0 ...>>], @super_group=#<Company:0x007f85730c7060 ...>>, #<GeneralAffairsDep:0x007f85730c69d0 @sub_groups= [#<FinanceDiv:0x007f85730c6930 @budget=120, @member_count=2, @super_group=#<GeneralAffairsDep:0x007f85730c69d0 ...>>, #<LegalDiv:0x007f85730c68e0 @budget=60, @member_count=1, @super_group=#<GeneralAffairsDep:0x007f85730c69d0 ...>>], @super_group=#<Company:0x007f85730c7060 ...>>]>
> company.member_count => 21 > company.budget => 1270
感想
composite, おもっていたより複雑だった。これまででいちばんパターン!って感じがする。
ここまでの感想
意外とデザインパターンって整理されておらず、ごちゃとアイデアが並べられているだけのような印象を受けた。 (異なるデザインパターンでも「人間からの見え方が異なるだけでシステム的には同じ構造」とかあるんだな。) 「コードはパターンにとってそれほど重要でないということを覚えておいてください。意図が重要です。」 デザインパターンはコンピュータのためのものではなく、人間のためのものなんだな。 だからこそ親しみやすいし、だからこそ(捉え方によっては)どこか軟弱な感じもする。