概要
「Rubyによるデザインパターン」を読んでデザインパターンを勉強中。
Javaをやっていた人間としての目線で情報を整理してみます。
今までに整理したもの
Template Method Pattern
Strategy Pattern
Observer Pattern
Composite Pattern
Command Pattern
Proxy Pattern
Decorator Pattern
Factory pattern
- 「オブジェクトの生成」をカプセル化する、というアイデア
- GoF のデザインパターンでは「Factory Method」と「Abstract Factory」の2つ
これまでなんとなくモヤッとしていたんですが「Rubyによるデザインパターン」にとてもシンプルで分かりやすい解説があったので引用します。
In the same way that the Factory Method pattern is really the Template Method pattern applied to object creation, so the Abstract Factory pattern is simply the Strategy pattern applied to the same problem.
オブジェクトの生成に Template Method を適用したものが Factory Method, Strategy を適用したものが Abstract Factory だ、ということです。詳細は以下で。
Factory Method pattern
オブジェクト生成を子クラスに移譲する。
###実装例
class MisoSoup
attr_reader :name
def prepare
puts "Preparing"
end
def make_dashi
puts "Make dashi"
end
def boil
puts "Boil"
end
def add_miso
puts "Add miso"
end
end
class KantoStyleMisoSoup < MisoSoup
def initialize
@name = "Kanto Style"
end
end
class KansaiStyleMisoSoup < MisoSoup
def initialize
@name = "Kansai Style"
end
end
class JapaneseRestaurant
# オブジェクトの生成部分を子クラスに移譲した template method とする
def order_miso_soup
# soup = KantoStyleMisoSoup.new とせずに抽象化する
soup = new_miso_soup
soup.prepare # 下拵え
soup.boil # 具材を入れつつ煮立たせる
soup.add_miso # みそ投入
soup
end
def new_miso_soup
raise "Must be override!"
end
end
class KantoStyleRestaurant < JapaneseRestaurant
# このように具体的なオブジェクトを生成することを目的としたメソッドを factory method と言う
def new_miso_soup
KantoStyleMisoSoup.new
end
end
class KansaiStyleRestaurant < JapaneseRestaurant
# 子クラスで factory method を override
def new_miso_soup
KansaiStyleMisoSoup.new
end
end
restaurant = KantoStyleRestaurant.new
miso_soup = restaurant.order_miso_soup
puts "Got a cup of #{miso_soup.name} miso soup."
Abstract Factory pattern
オブジェクトの生成を別のオブジェクトに移譲。
特定のオブジェクトの組み合わせを生成することを目的としたクラス(Abstract Factory)を定義。
###実装例
class MisoSoup
attr_reader :dashi, :miso, :ingrediants
def initialize(factory)
@factory = factory
end
def prepare
# ingrediants, dashi, miso オブジェクトの生成は factory オブジェクトに移譲
# この部分が Strategy pattern となっている
@ingrediants = @factory.createIngrediants
@dashi = @factory.createDashi
@miso = @factory.createMiso
puts "Preparing #{@ingrediants.join(', ')}."
end
def make_dashi
puts "Make dashi from #{@dashi}"
end
def boil
puts "Boil #{@ingrediants.join(', ')}."
end
def add_miso
puts "Add #{@miso} into the pot."
end
end
class KantoStyleMisoSoupFactory
# Abstract Factory の実装として各オブジェクトの生成メソッドは factory method である場合も多い
def createMiso
AkaMiso.new
end
def createDashi
Katsuo.new
end
def createIngrediants
[Tofu.new, Wakame.new, Negi.new]
end
end
class KansaiStyleMisoSoupFactory
def createMiso
ShiroMiso.new
end
def createDashi
Kombu.new
end
def createIngrediants
[Tofu.new, Wakame.new, Negi.new]
end
end
class JapaneseRestaurant
def initialize(factory)
@factory = factory
end
def order_miso_soup
soup = MisoSoup.new(@factory)
soup.prepare # 下拵え
soup.boil # 具材を入れつつ煮立たせる
soup.add_miso # みそ投入
soup
end
end
restaurant = JapaneseRestaurant.new(KantoStyleMisoSoupFactory.new)
miso_soup = restaurant.order_miso_soup
###より Ruby らしく
Ruby においてクラスは Class クラスのインスタンスであるので、クラス自体をコンストラクタの引数で渡すようにすればより抽象化された Factory クラスにできる。
class MisoSoupFactory
def initialize(miso_class, dashi_class, *ingrediant_classes)
@miso_class = miso_class
@dashi_class = dashi_class
@ingrediant_classes = ingrediant_classes
end
# Ruby でのインスタンス生成はあくまでもそのクラスオブジェクトの new メソッドを呼び出しているだけなので
# 次のような形でシンプルに記述できる
def createMiso
@miso_class.new
end
def createDashi
@dashi_class.new
end
def createIngrediants
list = []
@ingrediant_classes.each do |ingrediant_class|
list << ingrediant_class.new
end
list
end
end
# Factory クラスのコンストラクタに必要なクラスオブジェクトを渡す
factory = MisoSoupFactory.new(AkaMiso, Katsuo, Tofu, Wakame, Negi)
restaurant = JapaneseRestaurant.new(factory)
miso_soup = restaurant.order_miso_soup
####Javaの場合
次のようにすればインスタンス生成時にクラスを動的に変更できますが、型情報を抽象化するのはある意味で Java の利点を損なうことになるので多用すべきではない、と個人的には思います。
String name = "AkaMiso";
Class.forName(className).newInstance();
// または
Class clazz = AkaMiso.class;
clazz.newInstance();
参考
Olsen, R. 2007. Design Patterns in Ruby