k-tokitoh

Rubyによるデザインパターン - template method, strategy, command, observer, composite

2019-05-08

[![Rubyによるデザインパターン](https://images-fe.ssl-images- amazon.com/images/I/41PNvUxHtgL.SL160.jpg)](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
# (抽象)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, おもっていたより複雑だった。これまででいちばんパターン!って感じがする。

ここまでの感想

意外とイデアが並べられているだけのような印象を受けた。 (異なるデザインパターンでも「人間からの見え方が異なるだけでシステム的には同じ構造」とかあるんだな。) 「コードはパターンにとってそれほど重要でないということを覚えておいてください。意図が重要です。」 デザインパターンはコンピュータのためのものではなく、人間のためのものなんだな。 だからこそ親しみやすいし、だからこそ(捉え方によっては)どこか軟弱な感じもする。