4. Rubyによるデザインパターン【Factory Method】
4回目のデザインパターンは Factory Method(ファクトリーメソッド) パターンの記事を書いてみる。
Template Method と関連性のあるパターン。
実装については簡単だけど、メリットが理解しづらい。
いろいろなサイトを参考にして、3つのメリットを理解したので記事の中で書いていく。
おなじみ、下記ソウルを忘れないこと。
参考:programming - Rubyによるデザインパターン5原則 - Qiita
調査
Factory Method パターン - Wikipedia
Factory Method パターンは、他のクラスのコンストラクタをサブクラスで上書き可能な自分のメソッドに置き換えることで、 アプリケーションに特化したオブジェクトの生成をサブクラスに追い出し、クラスの再利用性を高めることを目的とする。
Wikiの図の説明だが、 まず Creator という抽象クラスの定義について、 anOperation メソッドと factoryMethod メソッドが定義されているが、 anOperation メソッド内で factoryMethod が呼ばれるものとする。
factoryMethod メソッドは Product クラス型のインスタンスを生成して返却する。
ConcreteCreator クラスは Creator クラスを継承して定義。
factoryMethod をオーバーライドし、Product クラスを継承した ConcreteProduct クラスのインスタンスを生成して返却する。
概念
博士 # Creator 魔物を呼び出す # anOperation 魔物を作る # factoryMethod 魔物が標的を選ぶ 魔物が標的を襲う 魔物を作る # factoryMethod(オーバライドされる前提 魔物 # Product 標的を選ぶ 標的を襲う ビバルディ < 博士 # ConcreteCreator 魔物を作る # factoryMethod のオーバーライド 魔人プウを生成 魔人プウ < 魔物 # ConcreteProduct 標的を選ぶ 標的を襲う 標的をガムにする ドクターゲボ < 博士 魔物を作る 人造怪人を生成 人造怪人 < 魔物 標的を選ぶ 標的を襲う 標的の脂肪を吸引する 魔王 人を襲わせる ビバルディを生成.魔物を呼び出させる ドクターゲボを生成.魔物を呼び出させる
ただ、言葉にしてみただけ感・・・
博士 = Creator 魔物 = Product ビバルディ = ドクターゲボ = ConcreteCreator 魔人プウ = 人造怪人 = ConrcreteProduct 魔王 = 博士を利用するクラス
上記のような割当となる。
魔王視点で考えるならば、魔物を意識せずに標的を襲わせることができる。
つまり、いちいち魔王が魔物の操作をしなくて済むし、博士に魔物を呼び出せと命令するだけでいい。
これが Factory Method の1つ目のメリットだと考える。(Template Method のメリットとも言えるw)
博士の魔物を呼び出すメソッドが Template Method になっていて、
魔物を作るメソッドが Factory Method になっている。
以下、魔物を作るメソッドに該当するものをファクトリーメソッドと呼ぶ。
博士クラスの魔物を呼び出すメソッドが「魔物を生成して標的を襲うまで」のアルゴリズムを持っているので、 博士クラスを継承するビバルディ・ドクターゲボはこの Template Method の恩恵にあずかることができる。
実装
class Dr def call_monster monster = create_monster monster.select_target monster.attack_to_target end def create_monster;end end class Monster attr_accessor :target def select_target;end def attack_to_target;end end class DrGebo < Dr def create_monster MysteryMan.new end end class MysteryMan < Monster def select_target search_by_radar end def attack_to_target p "attack to #{@target}" end private def search_by_radar @target = "ririkun" end end class Vivaldi < Dr def create_monster Puu.new end end class Puu < Monster def select_target search_by_smell end def attack_to_target p "attack to #{@target}" end private def search_by_smell @target = "Mr. Zatan" end end class Maou def self.attack DrGebo.new.call_monster Vivaldi.new.call_monster end end Maou.attack # 実行結果 # "attack to ririkun" # "attack to Mr. Zatan"
実装を通して見えてくる2つ目のメリットは、
create_monster メソッド(ファクトリーメソッド)がサブクラスへ切り出されているおかげで、
開発が進めやすいというところ。
違う言い方をするなら、結合度が低くなり独立性が高まる。
Dr のサブクラス担当者はこのインスタンスをモックに置き換えて実装しておくことで、 Maou クラス(利用側)担当者の実装を滞らせることはなくなるし、 Monster のサブクラスの実装を分離して進められる。
複数人で共通ブランチを弄る場合、モック置き換えが活きる。
開発中のバグがある実装をコミットしても、モックのおかげで誰の実装にも影響を与えなくて済む。
テストを書いて実装が終わったなら、モックを本来のインスタンス生成に置き換えれば、これまた誰の実装に影響もなく済む。
そして、わかりづらい3つ目のメリットは、
Dr, Monster クラスの担当者がサブクラスを全く気にせずに実装を進められる。
つまり、フレームワーク側を作る側にメリットがある。
- 俺日記 : Factory Methodパターンの本当のメリットってなんだろう…。
- What is the difference between Factory and Strategy patterns? - Stack Overflow
- Difference between Factory Method and Strategy design patterns | Deadschool
↑のサイトを何十回も読み直し・・・
Factory Method パターンの親クラスにあたる実装をフレームワークとして捉えた場合、 サブクラスの具象名や実装を全く気にしないで実装を進めることができる。
またフレームワークを提供するに辺り、 サブクラス実装者にインスタンスの生成を委ねられることが、 フレームワークとしてのあり方でもある。
でもわざわざ Factory Method にしなくても、利用側でインスタンス生成して渡せばいいのでは感もある。 つまり、Strategy パターンを適用した場合どうなるのか。
class Dr def initialize(monster) @monster = monster end def attack @monster.attack # monster に委譲 end end class Monster def attack; end; # インターフェースの統一 end class MysteryMan < Monster attr_accessor :target def attack # インターフェースの実装 search_by_radar attack_to_target end def search_by_radar @target = "ririkun" end def attack_to_target p "attack to #{@target}" end end class Maou def self.attack Dr.new(MysteryMan.new).attack end end Maou.attack # 実行結果 # "attack to ririkun"
実装のキモが MysteryMan (委譲先)に寄り過ぎてフレームワークとしてあまり価値のない状態になった。
Strategy パターンはやはり、アルゴリズムの選択に意味があるのであって、実装にルールを作るフレームワークの要件に合っていない。
Template Method パターンならどうなのか。
# 生成に関する Dr は必要なくなるので実装しない class Monster def attack select_target attack_to_target end def select_target; end def attack_to_target; end end class MysteryMan < Monster def select_target ...
まず、Template Method は継承なので結合度が高い。
Factory Method だと、インスタンスを生成して、それに対して処理を委譲するので結合度は低くなる。
Template Method だと実装が増えるにつれて結合度が増していき、管理しづらいコードになっていくと予想される。
(コード量が多くないなら気にならないが。)
では、Template Method パターン内で処理を委譲するインスタンス生成をさせたらいいのでは。
class MysteryMan < Monster def select_target hoge = Hoge.new ...
処理を Hoge インスタンスに委譲できたとしても、 インスタンス生成がベタ書きになるので結合度は高く、 変更に弱いし、並列開発しづらい状態になる。
ファクトリーメソッド の改造
長いけど、さらに補足。
wiki には下記のようなことが書いてあった。
factoryMethodは、デフォルトの動作を含んだ具象メソッドである場合もある。パラメータを取り、それによって生成するクラスを変えることもある。
これはどういうことなのかというと、
class Vivaldi < Dr def create_monster(name) if name == "puu" Puu.new else Ruu.new end end end
生成するインスタンスの種類が増えているが、 ファクトリーメソッドによって生成するインスタンスをサブクラスが選んでいる。
利用する側はインスタンス生成に関する分岐を意識しなくて済むことがメリットになる。
ruby っぽく書くなら下記。
class Vivaldi < Dr def create_monster(name) Object.const_get(name.capitalize).new rescue nil end end
まとめ
サブクラスにファクトリーメソッドをオーバーライドさせてインスタンス生成させときゃいいんだろって、 言葉の上でもソース上でも簡単そうな Factory Method パターン。
実際はどういう角度でこのパターンを見るかによって、 メリットや使いどころが大きく変わるので理解が難しい。
- 利用する側(ConcreteCreator を呼び出す側)は気軽に使える
- ファクトリーメソッドによって生成されるインスタンスをモックに置き換えることで、みんなの並列開発が捗る
- フレームワーク開発のためのパターンとも言える
ただ単にインスタンス生成に無理矢理このパターンを使っても全くメリットがないし、 むしろ、ソースが読みづらくなるだけのデメリットしかない。
使いどころ
個人的な使いどころの見解としては、
- 軽いフレームワーク作り = Template Method
- 軽くないフレームワーク作り = Factory Method(+ Template Method)
- インスタンス生成すべき種類が多い = Factory Method
- インスタンス生成過程が複雑 = Builder
インスタンス生成すべき種類が多い場合には、 ConcreteCreator + ConcreteProduct を増やすのではなく、 ConcreteCreator のファクトリーメソッド内に分岐を作り、 ConcreteProduct を増やして インスタンスを作り分ける実装をするといい。
おさらい
もっと単純にまとめよう。
Factory Method は下記のとき使う。
次回
次回は Abstract Factory やな。