2019-05-08
Rubyによるデザインパターン - template method, strategy, command, observer, composite
[](http://www.amazon.co.jp/exec/obidos/ASIN/4894712857/hatena- blog-22/)
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, おもっていたより複雑だった。これまででいちばんパターン!って感じがする。
ここまでの感想
意外とイデアが並べられているだけのような印象を受けた。 (異なるデザインパターンでも「人間からの見え方が異なるだけでシステム的には同じ構造」とかあるんだな。) 「コードはパターンにとってそれほど重要でないということを覚えておいてください。意図が重要です。」 デザインパターンはコンピュータのためのものではなく、人間のためのものなんだな。 だからこそ親しみやすいし、だからこそ(捉え方によっては)どこか軟弱な感じもする。