Factory
Rubyによるデザインパターンの勉強です。残りわずかになってきました。
提供は、
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 12人 クリック: 193回
- この商品を含むブログ (58件) を見る
本日は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
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