Abstract Factoryパターン

概要

一言で言うと、「インスタンスの生成を専門に行うクラスを用意することで、間違いないように(複雑な)オブジェクトをつくる」パターンです。

例題

ピザを作ることを考えます。

  • ピザは生地、ソース、トッピングからできます。
  • 工場Aでは、生地に小麦、ソースにトマト、トッピングにコーンを使用します。
  • 工場Bでは、生地に米粉、ソースにバジル、トッピングにチーズを使用します。
  • すべての素材に確認のためのメソッド(check)をもたせます。



なお、クラスを英語名にするにあたり、以下のように翻訳しています。

日本語 英語
生地 dough
小麦 wheat
米粉 rice flour

サンプルコード

本サイトにおけるスタイルはある程度統一しています。
詳細は「本サイトにおけるコーディングスタイル」を参照してください。

amount_dict = {"high": 1.2, "normal": 1.0, "low": 0.8}


def main():
    factorya = AbstractPizzaFactory(PizzaFactoryA())
    pizza1 = factorya.make_pizza("high")
    pizza1.check_pizza()

    print("-----")

    factoryb = AbstractPizzaFactory(PizzaFactoryB())
    pizza2 = factoryb.make_pizza("normal")
    pizza2.check_pizza()


# AbstractFactory
class AbstractPizzaFactory:
    def __init__(self, pizza_factory, amount_str="normal"):
        self.factroy = pizza_factory

    def make_pizza(self, amount_str):
        amount = amount_dict[amount_str]
        self.pizza_materials = []
        self.pizza_materials.append(self.factory.add_dough(amount))
        self.pizza_materials.append(self.factory.add_source(amount))
        self.pizza_materials.append(self.factory.add_topping(amount))

    def check_pizza(self):
        for pizza_material in self.pizza_materials:
            pizza_material.check()

    # createproduct
    def add_dough(self, amount=1):
        pass

    # createproduct
    def add_source(self, amount=1):
        pass

    # createproduct
    def add_topping(self, amount=1):
        pass


# ConcreteFactory
class PizzaFactoryA(AbstractPizzaFactory):
    def __init__(self):
        pass

    # createproduct
    def add_dough(self, amount=1):
        return WheatDough(amount)

    # createproduct
    def add_source(self, amount=1):
        return TomatoSource(amount)

    # createproduct
    def add_topping(self, amount=1):
        return CoanTopping(amount)


# ConcreteFactory
class PizzaFactoryB(AbstractPizzaFactory):
    def __init__(self):
        pass

    # createproduct
    def add_dough(self, amount=1):
        return RiceFlourDough(amount)

    # createproduct
    def add_source(self, amount=1):
        return BasilSource(amount)

    # createproduct
    def add_topping(self, amount=1):
        return CheeseTopping(amount)


# この場合は__init__は共通のため、子クラスでは__init__しません。
# ConcreteProduct
class Dough:
    def __init__(self, amount):
        self.amount = amount

    def check(self):
        pass


# ConcreteProduct
class WheatDough(Dough):
    def check(self):
        print("Wheat(amount: {})".format(self.amount))


# ConcreteProduct
class RiceFlourDough(Dough):
    def check(self):
        print("FlourDough(amount: {})".format(self.amount))


# ConcreteProduct
class Source:
    def __init__(self, amount):
        self.amount = amount

    def check(self):
        pass


# ConcreteProduct
class TomatoSource(Source):
    def check(self):
        print("Tomato(amount: {})".format(self.amount))


# ConcreteProduct
class BasilSource(Source):
    def check(self):
        print("Basil(amount: {})".format(self.amount))


# ConcreteProduct
class Topping:
    def __init__(self, amount):
        self.amount = amount

    def check(self):
        pass


# ConcreteProduct
class CoanTopping(Topping):
    def check(self):
        print("Coan(amount: {})".format(self.amount))


# ConcreteProduct
class CheeseTopping(Topping):
    def check(self):
        print("Cheese(amount: {})".format(self.amount))


if __name__ == "__main__":
    main()

# 結果

Wheat(amount: 1.2) Tomato(amount: 1.2) Coan(amount: 1.2) ----- FlourDough(amount: 1.0) Tomato(amount: 1.0) Cheese(amount: 1.0)

特徴

ピザ作成には「生地、ソース、トッピング」が必要であり(素材)、それぞれを「配列に入れる」ことでピザが完成します(作り方)。

AbstractFactoryは、「素材」と「作り方」があまり変わらないときに、これらを抽象化するパターンです。

これにより、別のピザが作りたくなったときは、別の工場を作ることで、作るものの入れ替えを簡素化します。

ソースコードにおけるピザの生地の作成の流れは以下の様になっています。

AbstractPizzaFactoryの中でself.factory.add_dough()が実行される
self.factoryがPizzaFactoryAのインスタンスのため、PizzaFactoryAクラスのadd_dough() が呼ばれる という流れです。

残りのソース、トッピングもほぼ同じように作っています。

class AbstractPizzaFactory:
    def __init__(self, pizza_factory, amount_str="normal"):
        self.factroy = pizza_factory

    def make_pizza(self, amount_str):
        self.pizza_materials.append(self.factory.add_dough(amount))
        ...

class PizzaFactoryA(AbstractPizzaFactory):
    def __init__(self):
        pass

    def add_dough(self, amount=1):
        return WheatDough(amount)

class WheatDough(Dough):
    ...

Pythonらしく書く

1コ目ですが、難しいので、慣れていない場合は飛ばすのを推奨します。

クラス内クラスを使用します。

今回の例で行くと、
小麦生地クラス、トマトソースクラス、コーントッピングクラスは工場Aクラス専用のクラスであり、
米粉生地クラス、バジルソースクラス、チーズトッピングクラスは工場Bクラス専用のクラスです。

Pythonでは、クラスの中にクラスを含めることができます

そこで、今回は
工場Aクラスに小麦生地クラス、トマトソースクラス、コーントッピングクラスを
工場Bクラスに米粉生地クラス、バジルソースクラス、チーズトッピングクラスを
含めます。

ついでにいらない抽象クラスを色々消すと以下の様になります。

amount_dict = {"high": 1.2, "normal": 1.0, "low": 0.8}


def main():
    pizza1 = make_pizza(PizzaFactoryA, "high")
    pizza1.check_pizza()

    print("-----")

    pizza2 = make_pizza(PizzaFactoryB, "normal")
    pizza2.check_pizza()


def make_pizza(PizzaFactory, amount_str):
    pizza = PizzaFactory()
    amount = amount_dict[amount_str]
    pizza.pizza_materials = []
    pizza.pizza_materials.append(pizza.add_dough(amount))
    pizza.pizza_materials.append(pizza.add_source(amount))
    pizza.pizza_materials.append(pizza.add_topping(amount))
    return pizza


class PizzaFactoryA:
    def __init__(self):
        pass

    def check_pizza(self):
        for pizza_material in self.pizza_materials:
            pizza_material.check()

    @classmethod
    def add_dough(Class, amount=1):
        return Class.WheatDough(amount)

    @classmethod
    def add_source(Class, amount=1):
        return Class.TomatoSource(amount)

    @classmethod
    def add_topping(Class, amount=1):
        return Class.CoanTopping(amount)

    class WheatDough:
        def __init__(self, amount=1):
            self.amount = amount

        def check(self):
            print("Wheat(amount: {})".format(self.amount))

    class TomatoSource:
        def __init__(self, amount=1):
            self.amount = amount

        def check(self):
            print("Tomato(amount: {})".format(self.amount))

    class CoanTopping:
        def __init__(self, amount=1):
            self.amount = amount

        def check(self):
            print("Coan(amount: {})".format(self.amount))


class PizzaFactoryB:
    def __init__(self):
        pass

    def check_pizza(self):
        for pizza_material in self.pizza_materials:
            pizza_material.check()

    @classmethod
    def add_dough(Class, amount=1):
        return Class.RiceFlourDough(amount)

    @classmethod
    def add_source(Class, amount=1):
        return Class.BasilSource(amount)

    @classmethod
    def add_topping(Class, amount=1):
        return Class.CheeseTopping(amount)

    class RiceFlourDough:
        def __init__(self, amount=1):
            self.amount = amount

        def check(self):
            print("FlourDough(amount: {})".format(self.amount))

    class BasilSource:
        def __init__(self, amount=1):
            self.amount = amount

        def check(self):
            print("Tomato(amount: {})".format(self.amount))

    class CheeseTopping:
        def __init__(self, amount=1):
            self.amount = amount

        def check(self):
            print("Cheese(amount: {})".format(self.amount))


if __name__ == "__main__":
    main()

かなりソースコードが簡易化されたのがわかるかと思います。
なお、出力は先程と同じです。

クラス図

# 一般的なクラス図

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

# サンプルコードのクラス図

Abstract Factoryパターンのクラス図(サンプルコードのクラス図)

# Pythonicなサンプルコードのクラス図

Abstract Factoryパターンのクラス図(Pythonicなサンプルコードのクラス図)

参考

実践Python3
→ Pythonらしく書くの見出し部分は実践Python3よりお借りしました。

HeadFirst
→ 例題のアイデアをお借りしました。