Pythonでデザインパターンを学ぶ:ファクトリー

プログラミング

Pythonでデザインパターンを学ぶ:ファクトリー

ファクトリーメソッド・パターン

ファクトリーメソッド・パターンは、オブジェクト生成に関するデザインパターンの一つです。その目的は、オブジェクトの生成ロジックをサブクラスに委譲することにあります。これにより、コードの柔軟性と拡張性を高めることができます。

基本的な考え方

ファクトリーメソッド・パターンでは、親クラスに「ファクトリーメソッド」と呼ばれる抽象的なメソッドを定義します。このファクトリーメソッドは、具体的なオブジェクトを生成する役割を担いますが、その生成ロジック自体は具象クラスで実装されます。

クライアントコードは、親クラスのインターフェースを通じてオブジェクトを要求します。どの具象クラスのオブジェクトが生成されるかは、クライアントコードからは隠蔽され、サブクラスの判断に委ねられます。

Pythonでの実装例

Pythonでは、抽象基底クラス(abcモジュール)を利用してファクトリーメソッド・パターンを表現することが一般的です。

from abc import ABC, abstractmethod

class Product(ABC):
    @abstractmethod
    def operation(self):
        pass

class ConcreteProductA(Product):
    def operation(self):
        return "ConcreteProductA operation"

class ConcreteProductB(Product):
    def operation(self):
        return "ConcreteProductB operation"

class Creator(ABC):
    @abstractmethod
    def factory_method(self) -> Product:
        pass

    def some_operation(self) -> str:
        product = self.factory_method()
        return f"Creator: {product.operation()}"

class ConcreteCreatorA(Creator):
    def factory_method(self) -> Product:
        return ConcreteProductA()

class ConcreteCreatorB(Creator):
    def factory_method(self) -> Product:
        return ConcreteProductB()

# クライアントコード
creator_a = ConcreteCreatorA()
print(creator_a.some_operation())

creator_b = ConcreteCreatorB()
print(creator_b.some_operation())

この例では、Productという抽象基底クラスがあり、ConcreteProductAConcreteProductBがその具象クラスです。Creatorという抽象基底クラスにはfactory_methodがあり、ConcreteCreatorAConcreteCreatorBがそれぞれ具体的な製品を生成するfactory_methodを実装しています。some_operationメソッドは、factory_methodで生成された製品のoperationを呼び出します。

利点

  • 疎結合: クライアントコードは、生成される具体的な製品クラスを知る必要がありません。
  • 拡張性: 新しい製品やクリエーターを追加するのが容易です。既存のコードを変更せずに、新しいProductCreatorのサブクラスを追加できます。
  • 単一責任の原則: オブジェクト生成の責任が、各具象クリエータークラスに分離されます。

欠点

  • クラス数の増加: 新しい製品を追加するたびに、新しいProductクラスとCreatorクラスが必要になるため、クラスの数が増加する可能性があります。

ファクトリー・クラス・パターン (Abstract Factory)

Abstract Factoryパターンは、関連するオブジェクト群を生成するためのインターフェースを提供しますが、その具体的なクラスはサブクラスで決定されます。これは、複数の種類の製品をまとめて生成したい場合に有用です。

基本的な考え方

Abstract Factoryパターンでは、抽象的な「AbstractFactory」インターフェースを定義します。このインターフェースには、複数の「ファクトリーメソッド」が含まれており、それぞれが異なる種類の製品を生成します。

具体的な「ConcreteFactory」クラスは、このAbstractFactoryインターフェースを実装し、各ファクトリーメソッドで具体的な製品を生成します。クライアントコードは、AbstractFactoryインターフェースを通じて製品を要求し、どのConcreteFactoryが使用されるかによって、生成される製品のファミリーが決まります。

Pythonでの実装例

Pythonでは、こちらもabcモジュールを用いて実装できます。

from abc import ABC, abstractmethod

# 抽象製品A
class AbstractProductA(ABC):
    @abstractmethod
    def operation_a(self) -> str:
        pass

# 具体製品A1
class ConcreteProductA1(AbstractProductA):
    def operation_a(self) -> str:
        return "ConcreteProductA1"

# 具体製品A2
class ConcreteProductA2(AbstractProductA):
    def operation_a(self) -> str:
        return "ConcreteProductA2"

# 抽象製品B
class AbstractProductB(ABC):
    @abstractmethod
    def operation_b(self) -> str:
        pass

# 具体製品B1
class ConcreteProductB1(AbstractProductB):
    def operation_b(self) -> str:
        return "ConcreteProductB1"

# 具体製品B2
class ConcreteProductB2(AbstractProductB):
    def operation_b(self) -> str:
        return "ConcreteProductB2"

# 抽象ファクトリー
class AbstractFactory(ABC):
    @abstractmethod
    def create_product_a(self) -> AbstractProductA:
        pass

    @abstractmethod
    def create_product_b(self) -> AbstractProductB:
        pass

# 具体ファクトリー1
class ConcreteFactory1(AbstractFactory):
    def create_product_a(self) -> AbstractProductA:
        return ConcreteProductA1()

    def create_product_b(self) -> AbstractProductB:
        return ConcreteProductB1()

# 具体ファクトリー2
class ConcreteFactory2(AbstractFactory):
    def create_product_a(self) -> AbstractProductA:
        return ConcreteProductA2()

    def create_product_b(self) -> AbstractProductB:
        return ConcreteProductB2()

# クライアントコード
def client_code(factory: AbstractFactory):
    product_a = factory.create_product_a()
    product_b = factory.create_product_b()

    print(f"Client: {product_a.operation_a()} and {product_b.operation_b()}")

print("Client: Testing with ConcreteFactory1:")
client_code(ConcreteFactory1())

print("nClient: Testing with ConcreteFactory2:")
client_code(ConcreteFactory2())

この例では、AbstractProductAAbstractProductBという2種類の抽象製品があり、それぞれにConcreteProductA1/ConcreteProductA2ConcreteProductB1/ConcreteProductB2という具体的な実装があります。AbstractFactoryは、これらの製品を生成するためのメソッドを定義し、ConcreteFactory1ConcreteFactory2が具体的な製品の組み合わせを生成します。client_code関数は、どのファクトリーが渡されても、同じインターフェースで製品を扱えます。

利点

  • 製品ファミリーの分離: 異なる製品ファミリーをまとめて切り替えることができます。
  • 一貫性の保証: 特定のファクトリーから生成された製品は、互いに互換性があることが保証されます。
  • 単一責任の原則: 具体的な製品を生成するロジックが、各具象ファクトリークラスにカプセル化されます。

欠点

  • 複雑さ: ファクトリーメソッド・パターンよりもクラス構造が複雑になる傾向があります。
  • 拡張性の制限: 新しい種類の製品を追加する場合、AbstractFactoryインターフェースとすべてのConcreteFactoryクラスを変更する必要があるため、拡張が困難になることがあります。

ファクトリー・パターンのその他

Pythonにおいては、上記で紹介したファクトリーメソッド・パターンとAbstract Factoryパターンが代表的ですが、これらはより広範な「ファクトリー」という概念の一部です。

Simple Factory (単純ファクトリー)

厳密にはデザインパターンとして分類されないこともありますが、オブジェクト生成を一つのクラスに集約させるアプローチです。これは、インターフェースや抽象クラスを介さず、単に特定の条件に基づいてオブジェクトを生成する関数やクラスメソッドとして実装されることが多いです。

例えば、以下のような形です。

class Dog:
    def speak(self):
        return "Woof!"

class Cat:
    def speak(self):
        return "Meow!"

class AnimalFactory:
    def create_animal(self, animal_type):
        if animal_type == "dog":
            return Dog()
        elif animal_type == "cat":
            return Cat()
        else:
            raise ValueError("Unknown animal type")

factory = AnimalFactory()
dog = factory.create_animal("dog")
print(dog.speak())

このSimple Factoryは、オブジェクト生成ロジックをクライアントコードから分離するという目的は果たしますが、ファクトリーメソッド・パターンやAbstract Factoryパターンが提供するような、サブクラスへの委譲や製品ファミリーの管理といった高度な柔軟性や拡張性は持ち合わせていません。

Pythonicなアプローチ

Pythonは動的型付け言語であるため、JavaやC++のような厳格なインターフェースや抽象クラスの定義が必須ではない場面もあります。しかし、デザインパターンを適用する際には、abcモジュールなどを用いて意図を明確にすることが、コードの可読性や保守性を高める上で重要です。

また、Pythonではデコレーターやメタクラスといった機能も、オブジェクト生成プロセスをカスタマイズするために利用されることがあります。これらは、直接的なファクトリーパターンとは異なりますが、広義にはオブジェクト生成の管理という文脈で関連する場合があります。

まとめ

ファクトリーパターン(ファクトリーメソッド、Abstract Factory)は、オブジェクト生成の複雑さを隠蔽し、コードの柔軟性、拡張性、保守性を向上させるための強力なツールです。

ファクトリーメソッド・パターンは、オブジェクト生成ロジックをサブクラスに委譲したい場合に適しています。一方、Abstract Factoryパターンは、関連するオブジェクト群をまとめて生成・管理したい場合に有効です。Simple Factoryは、より単純なオブジェクト生成の集約に利用できます。

これらのパターンを理解し、適切に適用することで、より堅牢で保守しやすいPythonコードを記述できるようになります。