Factory Methodパターン

概要

オブジェクト作成時に、作成するオブジェクトのクラスをサブクラスに選ばせる」パターンです。

例題

牛(Cow)を作る工場と鶏(Chicken)を作る工場があるとします。

出荷された動物のチェックとして餌を与え、鳴き声のチェックを行います。
(各animalに対し eat()speak() をさせる)

(「農場で育てる」の方が正しいですが、説明のために工場で作っていることにします)

サンプルコード

import abc


def main():
    cow = CowFactory()
    cow.check_animal()

    chiken = ChickenFactory()
    chiken.check_animal()


# Creator
class Factory:
    def __init__(self):
        self.animal = self.factory_method()

    # template_method
    def check_animal(self):
        self.animal.eat()
        self.animal.speak()

    @abc.abstractmethod
    def factory_method(self):
        pass


# Product
class Animal:
    @abc.abstractmethod
    def eat(self):
        pass

    @abc.abstractmethod
    def speak(self):
        pass


# ConcreteCreator
class CowFactory(Factory):
    def factory_method(self):
        return Cow()


# ConcreteCreator
class ChickenFactory(Factory):
    def factory_method(self):
        return Chicken()


# ConcreteProduct
class Cow(Animal):
    def eat(self):
        print("Cow:eat")

    def speak(self):
        print("Cow:speak")


# ConcreteProduct
class Chicken(Animal):
    def eat(self):
        print("Chiken:eat")

    def speak(self):
        print("Chiken:speak")


if __name__ == "__main__":
    main()

# 出力

Cow:eat Cow:speak Chiken:eat Chiken:speak

特徴

大事なのは以下の部分です。

class Factory:
    def __init__(self):
        self.animal = self.factory_method()

    # check_animal関数

    @abc.abstractmethod
    def factory_method(self):
        pass

self.animal に対し、self.factory_method() 関数で作成したものを代入していますが、
self.factory_method() 自体は抽象メソッドであり、子クラスにオブジェクトの生成を選択させています。

たとえば、以下のようなコードでも対応できます。

class Factory:
    def __init__(self, animal_name):
        if animal_name == "cow":
            self.animal = Cow()
        elif animal_name == "chicken":
            self.animal = Chicken()

    # あと同じ

しかし、このように書くと、牛、鶏以外に新しいクラスを追加する時に、
if文も書き換える必要が出てきてしまいます。

なお、今回の例のように、簡単なコードだったらif文で対応してもよいと思うので、
その辺は変更の起きやすさ、コードの複雑さと相談でしょう。

Pythonらしく書くためのコツ

2つ省略できるポイントがあります。

① Pythonは動的型付け言語なため、Animalクラスは省略することのほうが多いです。

②「Pythonはクラスが第一級オブジェクトである」が使えます。

第一級オブジェクトとは簡単にいうと、「変数に入れたりすることができる」と思っていてOKです。
(詳細については上記リンク先に記載しています。)

簡単な例を示します。

class Hoge:
    def test(self):
        print("hoge")

hoge_class_copied = Hoge
hoge_instance = hoge_class_copied()
hoge_instance.test()  # => hoge

以上のように、Hogeを一度変数に入れても使用することができます。

この特性を使うと、factory_methodが不要になります。

以下にサンプルコードを示します。

サンプルコード - PythonicなFactory Method

def main():
    cow_factory = Factory(Cow)
    cow_factory.check_animal()

    chiken_factroy = Factory(Chicken)
    chiken_factroy.check_animal()


class Factory:
    def __init__(self, animal_class):
        self.animal = animal_class()

    def check_animal(self):
        self.animal.eat()
        self.animal.speak()


# * Animalクラスを継承しなくなった
# クラスの中身は1コ目のサンプルコードと同じであるため省略
class Cow:
    pass


# * Animalクラスを継承しなくなった
# クラスの中身は1コ目のサンプルコードと同じであるため省略
class Chicken:
    pass


if __name__ == "__main__":
    main()

出力結果は1コ目のサンプルコードと全く同じです。

一番大事なところは self.animal = animal_class() の1行で、ここでクラスを指定することで
オブジェクト作成時に、作成するオブジェクトのクラスをサブクラスに選ばせる」ことを実現しています。

クラス図

# 一般的なクラス図

一般的なクラス図(Factory Methodパターン)

# サンプルコードにおけるクラス図

サンプルコードにおけるクラス図(Factory Methodパターン)

# サンプルコード(PythonicなFactory Method)におけるクラス図

Pythonicなサンプルコードにおけるクラス図(Factory Methodパターン)

参考

Factory Methodの概要