読者です 読者をやめる 読者になる 読者になる

Shred IT!!!!

IT全般について試したこと・勉強したことを綴ったり、趣味について語るブログ

4. Rubyによるデザインパターン【Factory Method】

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

4回目のデザインパターンは Factory Method(ファクトリーメソッド) パターンの記事を書いてみる。

Template Method と関連性のあるパターン。

実装については簡単だけど、メリットが理解しづらい。
いろいろなサイトを参考にして、3つのメリットを理解したので記事の中で書いていく。


おなじみ、下記ソウルを忘れないこと。
参考:programming - Rubyによるデザインパターン5原則 - Qiita

  • 変わるものを変わらないものから分離する
  • プログラムはインターフェイスに対して行う(実装に対して行わない)
  • 継承より集約
  • 委譲、委譲、委譲
  • 必要になるまで作るな(YAGNI

調査

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 パターンの親クラスにあたる実装をフレームワークとして捉えた場合、 サブクラスの具象名や実装を全く気にしないで実装を進めることができる。

またフレームワークを提供するに辺り、 サブクラス実装者にインスタンスの生成を委ねられることが、 フレームワークとしてのあり方でもある。

でもわざわざ 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 + ConcreteProduct を増やすのではなく、 ConcreteCreator のファクトリーメソッド内に分岐を作り、 ConcreteProduct を増やして インスタンスを作り分ける実装をするといい。

おさらい

もっと単純にまとめよう。
Factory Method は下記のとき使う。

次回

次回は Abstract Factory やな。