k-tokitoh

2019-05-08

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

[![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

利用する場面

あるインスタンスの種類に応じて変えたい場合。

方法

注意すべき点

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 クラスに隠蔽する)

方法

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

利用する場面

あるオブジェクトで何かが起きたときに、他のオブジェクトに知らせたい(=他のオブジェクトの決まりきったメソッドを呼び出したい)とき。

方法

その他

サンプル

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
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, おもっていたより複雑だった。これまででいちばんパターン!って感じがする。

ここまでの感想

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