一言で言うと、「インスタンスの生成を専門に行うクラスを用意することで、間違いないように(複雑な)オブジェクトをつくる」パターンです。
ピザを作ることを考えます。
なお、クラスを英語名にするにあたり、以下のように翻訳しています。
日本語 | 英語 | |
---|---|---|
生地 | : | 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()
ピザ作成には「生地、ソース、トッピング」が必要であり(素材)、それぞれを「配列に入れる」ことでピザが完成します(作り方)。
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):
...
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()
かなりソースコードが簡易化されたのがわかるかと思います。
なお、出力は先程と同じです。
実践Python3
→ Pythonらしく書くの見出し部分は実践Python3よりお借りしました。
HeadFirst
→ 例題のアイデアをお借りしました。