13
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rubyデザインパターン 10日目 : Factory

Last updated at Posted at 2015-08-05

Rubyデザインパターン学習のために、自分なりに読書の結果をまとめていくことに決めました。第10日目はFactoryです。(http://www.amazon.co.jp/gp/product/4894712857/ref=as_li_qf_sp_asin_tl?ie=UTF8&camp=247&creative=1211&creativeASIN=4894712857&linkCode=as2&tag=morizyun00-22)

スクリーンショット 2015-07-27 11.25.28.png

 10日目 Factory

Factoryは、オブジェクトの生成とその動作部分を分けた上で、生成すべきオブジェクトを正しく選択するのためのパターンです。

Factoryの中でも二通りの表現方法があります。

  1. Factory Method
  2. Abstract Factory

これから順にサンプルコードとともに解説していこうと思います。

Factory サンプルコード

1. Factory Method

動物であるアヒルと、植物であるスイレン。どちらも池の中で生息している状態を表現してみましょう。


class Duck
  def initialize(name)
    @name = name
  end
  def eat
    puts("アヒル#{@name}は食事中です")
  end
  def speak
    puts("アヒル#{@name}がガーガー鳴いています")
  end
  def sleep
    puts("アヒル#{@name}は静かに眠っています")
  end
end

class WaterLily
  def initialize(name)
    @name = name
  end
  def grow
    puts "スイレン #{@name}は浮きながら日光を浴びて育ちます"
  end
end

class Pond
  def initialize(number_animals, animal_class, number_plants, plant_class)
    @animal_class = animal_class
    @plant_class = plant_class

    @animal = []
    number_animals.times do |i|
      animal = new_organism(:animal, "動物#{i}")
      @animals << animal
    end

    @plants = []
    number_plants.each do |i|
      plant = new_organism(:plant, "植物#{i}")
      @plants << plant
    end
  end

  def simulate_one_day
    @plants.each  {|plant| plant.glow }
    @animals.each {|animal| animal.speak }
    @animals.each {|animal| animal.eat   }
    @animals.each {|animal| animal.sleep }
  end

  def new_organism(type, name)
    if type == :animal
      @animal_class.new(name)
    elsif type == :plant
      @plant_class.new(name)
    else
      raise "Unknown organism type: #{type}"
    end
  end
end

pond = Pond.new(3, Duck, 2, WaterLily)
pond.simulate_one_day

=begin
スイレン 植物0は浮きながら日光を浴びて育ちます
スイレン 植物1は浮きながら日光を浴びて育ちます
アヒル動物0がガーガー鳴いています
アヒル動物1がガーガー鳴いています
アヒル動物2がガーガー鳴いています
アヒル動物0は食事中です
アヒル動物1は食事中です
アヒル動物2は食事中です
アヒル動物0は静かに眠っています
アヒル動物1は静かに眠っています
アヒル動物2は静かに眠っています
=end

Duckとアヒルの生活様式、WaterLilyはスイレンについての生活様式について述べるクラスです。それぞれ動物と植物なので、当然起こす行動も違います。
アヒルは寝たり食べたりしますが、スイレンは日光を浴びて成長するだけです。

どちらもPond(池)に生息する生き物なので、Pondクラスを作ってその二つを管理します。
simulate_one_dayはそれぞれの1日の生活を表現します。

作りたいオブジェクトのクラスをインスタンス変数に格納することで、非常に簡潔に書かれています。

こういった構成にすることで、池に住む動・植物だったら特になにも考えることなくどんどん追加できるでしょう。

実行時も、オブジェクトの数と種類を書くだけ。非常に簡潔です。

2. Abstract Factory

ここから機能を拡張する要望がきたとしましょう。池に住むオブジェクトだけではなく、ジャングル全体を扱うプログラムをつくる要望がきてしまいました。

まず私たちが取る行動は、先ほどのクラスの名前を変更して、とても単純な形で要望に答える短絡的な方法でやってみましょう。

Pondクラスの名前をHabitat(生息地)として、そのままそのクラスにトラや、ラフレシア、リスザルなどを入れることです。

それでもHabitatクラスはちゃんと動いてくれます。柔軟な設計していたおかげでしょう。

jungle = Habitat.new(1, Tiger, 4, Tree)
jungle.simulate_one_day

しかし、今の設計だと、一つだけ問題点があります。
以下のようなことができてしまうからです。

jungle = Habitat.new(1, Duck, 3, Rafflesia)
アヒルとラフレシアの組み合わせなども作れてしまいます。アヒルとラフレシアが共生している場面はあまり考えられませんよね。

こういったジャングルのように、想定されるオブジェクトが増えるにつれて、AbstractFactoryパターンの真価が発揮できます。

同じグループのオブジェクトをくくってしまいましょう

class PondOrganismFactory
  def new_animal(name)
    Frog.new(name)
  end

  def new_plant(name)
    Algae.new(name)
  end
end

class JungleOrganismFactory
  def new_animal(name)
    Tiger.new(name)
  end

  def new_plant(name)
    Tree.new(name)
  end
end

池に所属するものはPondOrganismFactory、ジャングルに所属するものはJungleOrganismFactoryにくくってまとめてしまいます。

これで、トラ(Tiger)と藻(Algae)のような、組み合わせたくないもの同士が実現しないような構成にすることができました。

ちなみに使いかたは下記のようになります。

class Habitat
  def initialize(number_animals, number_plants, organism_factory)
    @organism_factory = organism_factory

    @animals = []
    number_animals.times do |i|
      animal = @organism_factory.new_animal("動物#{i}")
      @animals << animal
    end

    @plants = []
    number_plants.times do |i|
      plant = @organism_factory.new_plant("植物#{i}")
      @plants << plant
    end
  end

  def simulate_one_day
    @plants.each  {|plant| plant.grow }
    @animals.each {|animal| animal.speak }
    @animals.each {|animal| animal.eat   }
    @animals.each {|animal| animal.sleep }
  end
end

class JungleOrganismFactory
  def new_animal(name)
    Tiger.new(name)
  end

  def new_plant(name)
    Tree.new(name)
  end
end

jungle = Habitat.new(1,4, JungleOrganismFactory.new)
jungle.simulate_one_day

これで柔軟性とともに、オブジェクトが増えても正しいクラスが選択できるような構成になってきました。

もう一つ改善するとすれば、Rubyの力でPondOrganismFactoryJungleOrganismFactoryなどのFactoryを一般化できないかということです。

Rubyらしく...

class OrganismFactory
  def initialize(plant_class, animal_class)
    @plant_class  = plant_class
    @animal_class = animal_class
  end
  
  def new_animal(name)
    @animal_class.new(name)
  end
  
  def new_plant(name)
    @plant_class.new(name)
  end
end

クラスもオブジェクトであるという考え方から、クラスをさらに一般化できました。

実行時にクラスオブジェクトを格納することで、カプセル化に成功しています。


jungle_organism_factory = OrganismFactory.new(Tree, Tiger)

jungle = Habitat.new(1, 4, jungle_organism_factory)
jungle.simulate_one_day

 まとめ

Factoryパターンは作ったクラスの中から、適したオブジェクトを選んできて、束ねる役割を果たします。そうすることで、オブジェクト生成を動作部分と分離することもできました。
また、Rubyのダックタイピングによってオブジェクト生成を管理するクラスを一般化し、柔軟性をあげることができました。
それと同時に、対象のオブジェクトをカプセル化することにも成功しました。

13
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?