Murayama blog.

プログラミング教育なブログ

Factory

Rubyによるデザインパターンの勉強です。残りわずかになってきました。
提供は、

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

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


本日はFactoryパターンを勉強します。
前回のSingletonと同様、Factoryパターンも有名なデザインパターンです。
GoFデザインパターンでは、
Factory Methodパターン、Abstract Factoryパターンに分類しています。
どちらもオブジェクトの生成を抽象化するデザインパターンになります。


ソースコードを解説する前に、本に載っているGoFデザインパターンの原則を引用しておきます。

  • デザインパターンの原則
    • 変わるものを変わらないものから分離する
    • インタフェースに対してプログラムし、実装に対して行わない
    • 継承より集約
    • 委譲、委譲、委譲


Factoryパターンは原則の一つ目がポイントになります。

  • 変わるものを変わらないものから分離する


それでは、本に載っているとおり、
「池でたたずんでいるアヒルの一日?」をイメージしたプログラムをFactoryパターンを見ていきます。


まずはアヒルを表すDuckクラスとDuckが存在する池となるPondクラスを作ってみます。

class Duck
  def initialize(name)
    @name = name
  end
  
  def speak
    puts("Duck #{@name} speak.")
  end
end

class Pond
  def initialize(number_ducks)
    @ducks = []
    number_ducks.times do |i|
      duck = Duck.new("Duck#{i}")
      @ducks << duck
    end
  end
  
  def simulate_one_day
    @ducks.each { |duck| duck.speak}
  end
end

Duckクラスには鳴く(speak)メソッドが定義されています。
Pondクラスはコンストラクタで、池で泳ぐアヒルの数を受け取ります。
また、ある日のシミュレートを行うsimulate_one_dayメソッドも定義しています。


それでは上記のクラスを実行する呼び出しもとプログラムを作成します。

Pond.new(3).simulate_one_day

3匹のアヒルが池で泳ぐ様子をシミュレートします。
実行結果は以下のようになります。

Duck Duck0 speak.
Duck Duck1 speak.
Duck Duck2 speak.


ここまではなんてことのないプログラムですね。
ここから、Factoryパターンの勉強になります。


ここで、仕様変更?が発生し、
さきほどのアヒル(Duck)クラスを、カエル(Frog)クラスに変更する必要があるとします。
カエル(Frog)クラスは以下のようになります。

class Frog
  def initialize(name)
    @name = name
  end

  def speak
    puts("Frog #{@name} speak.")
  end
end

カエル(Frog)クラスは、さきほどのアヒル(Duck)クラスと同じspeakメソッドを実装しています。
つまり、同じインタフェースで操作できるとも言えます。(Rubyの場合、ダックタイピングで操作できます)


問題は、Pondクラスをどのように変更するのか?というところです。
もう一度Pondクラスを見てみます。

class Pond
  def initialize(number_ducks)
    @ducks = []
    number_ducks.times do |i|
      duck = Duck.new("Duck#{i}")
      @ducks << duck
    end
  end
  
  def simulate_one_day
    @ducks.each { |duck| duck.speak}
  end
end

ここでDuckクラスに依存する部分に着目すると、

      duck = Duck.new("Duck#{i}")

の部分がDuckクラスに依存しているとわかります。
インスタンス変数の名前や、引数の名前がduckを含んでいる点は別途修正します。


ここで、冒頭に述べたデザインパターンの原則である

  • 変わるものを変わらないものから分離する

がポイントになります。


さきほどのDuckオブジェクトを生成している1行をメソッドとして切り出し、サブクラスに実装を任せることにします。
#あわせて変数名も修正します。

class Pond
  def initialize(number_animals)
    @animals = []
    number_animals.times do |i|
      animal = new_animal("Animal#{i}")
      @animals << animal
    end
  end
  
  def simulate_one_day
    @animals.each { |animal| animal.speak}
  end
end

class DuckPond < Pond
  def new_animal(name)
    Duck.new(name)
  end
end

class FrogPond < Pond
  def new_animal(name)
    Frog.new(name)
  end
end

PondクラスではDuck、Frogオブジェクトを生成する処理を抽象化しています。
実際のオブジェクト生成する処理はサブクラスであるDuckPondクラス、FrogPondクラスに任せています。
Gofは、クラスの選択をサブクラスに任せることをFactory Methodパターンと呼んでいます。


Factory Methodパターンをクラス図で表現すると以下のようになります。

Creator

    • Productオブジェクトを生成するクラス。実際にオブジェクトを生成するかはサブクラスに任せる。Pondクラスが該当する。

ConcreteCreator1、2

    • 特定のProductオブジェクトを生成するクラス。DuckPondクラス、FrogPondクラスが該当する。

Product

    • Creatorクラス(サブクラスも含む)によって生成されるクラス。Javaの場合はスーパークラスやインタフェースとなるが、Rubyの場合は不要。

Product1、2

    • Creatorクラスによって生成される具象クラス。Duckクラス、Frogクラスが該当する。


ここからAbstract Factoryパターンまで、もうちょっとだけ続きます。


つぎに、動物(Duck、Frog)以外にも植物を追加します。
ここでは、藻(Algae)、スイレン(WaterLily)クラスを作成します。

class Algae
  def initialize(name)
    @name = name
  end
  
  def grow
    puts "Algae #{@name} grow."
  end
end

class WaterLily
  def initialize(name)
    @name = name
  end
  
  def grow
    puts "WaterLily #{@name} grow."
  end
end

2つのクラスには植物の成長を示すgrowメソッドが定義されています。
これらのクラスのオブジェクトも生成できるようにPondクラスを変更します。

class Pond
  def initialize(number_animals, number_plants)
    @animals = []
    number_animals.times do |i|
      animal = new_animal("Animal#{i}")
      @animals << animal
    end
    
    @plants = []
    number_plants.times do |i|
      plant = new_plant("Plant#{i}")
      @plants << plant
    end
    
  end
  
  def simulate_one_day
    @plants.each { |plant| plant.grow}
    @animals.each { |animal| animal.speak}
  end
end

コンストラクタには、Algaeオブジェクト、WaterLilyオブジェクトを返すnew_plantメソッドを追加しています。
同様にsimulate_one_dayメソッドでも@plants変数にアクセスしています。


実際にAlgaeオブジェクトや、WaterLilyオブジェクトを作成するクラスは次のようになります。
#今回はDuckとWaterLily、FrogとAlgaeの組み合わせのみ有効とします。

class DuckWaterLilyPond < Pond
  def new_animal(name)
    Duck.new(name)
  end
  
  def new_plant(name)
    WaterLily.new(name)
  end
end

class FrogAlgaePond < Pond
  def new_animal(name)
    Frog.new(name)
  end
  
  def new_plant(name)
    Algae.new(name)
  end
end

ここまでくると、Abstract Factoryパターンと呼んでも良いと思います。
Abstract Factoryパターンは、矛盾のないオブジェクトの組み合わせを実現します。
このプログラムでは、DuckとWaterLily、FrogとAlgaeの組み合わせのみを許可しています。


最後にAbstarct Factoryパターンのクラス図です。

  • AbstractFactory
    • 複数のオブジェクト生成処理の組み合わせを定義するクラス(インタフェース)。Pondクラスが該当する。
  • ConcreteFactoryA、B
    • 特定のオブジェクトの組み合わせを実装するクラス。DuckWaterLilyPond、FrogAlgaePondクラスが該当する。
  • ProductA1、A2、B1、B2
    • CocreteFactoryA、Bによって生成されるクラス。DuckクラスWaterLilyクラスなどが該当する。


本では、ここから先もいろんな変更を加えていくんだけど、
長くなるのでFactoryの話題はこのへんで終わります。


最後にクラスオブジェクトを渡すFactoryについてもコードだけメモ。

class Pond
  def initialize(number_animals, animal_class, number_plants, plant_class)
    @animals = []
    number_animals.times do |i|
      animal = animal_class.new("Animal#{i}")
      @animals << animal
    end
    
    @plants = []
    number_plants.times do |i|
      plant = plant_class.new("Plant#{i}")
      @plants << plant
    end
  end
  
  def simulate_one_day
    @plants.each { |plant| plant.grow}
    @animals.each { |animal| animal.speak}
  end
end

呼び出しもとプログラムは以下のとおり。

Pond.new(3, Duck, 2, Algae).simulate_one_day

こっちの方がシンプルでRubyっぽくていいな。#Javaでもできるけど。