週末!プログラミング部

ソフトウェア開発ネタを中心に自分でいろいろ調べた内容を自分の勝手な解釈で思うがままに書いくためのブログ。サンプルソースコード、API、プラットフォーム、プログラミング言語、開発環境などを調査、分析して追求いく予定です。

Pythonでデザインパターン 肆ノ型「FactoryMethod」

今回はPythonの勉強しながらデザインパターンFactoryMethodをやってみました。
他のパターンにつきましては、以下の目次記事をご参照ください。

FactoryMethodとは

直訳すると「工場手続き」みたいな意味になります。
wikiには「他のクラスのコンストラクタをサブクラスで上書き可能な自分のメソッドに置き換えることで、 アプリケーションに特化したオブジェクトの生成をサブクラスに追い出し、クラスの再利用性を高める」とあります。

FactoryMethodについて考えてみる

wikiの文言だと私にはよくわからなかったので、 いつものごとく色々コードを書いて自分なりの理解を書いてみたいと思います(;^ω^)

まず以下のようなProductインタフェースとそれを実装したProductA クラスとProductB クラスがあったとします。

class Product(metaclass=ABCMeta) :
    @abstractmethod
    def functionA(self) :
        pass

class ProductA(Product) :
    def functionA(self) :
        # 機能Aを実装(処理内容は割愛)

class ProductB(Product) :
    def functionA(self) :
        # 機能Aを実装(処理内容は割愛)


これを使ったmainは以下のようなイメージになります。
このときProductAもProductBもインスタンスを作成後に、それぞれの専用初期化メソッドInitializeA、B、Cを順番に呼び出さなければならないものとします。
そうするとmainにProductA、ProductBに対するそれぞれの初期化メソッドを実装しなければならなくなります。 またProductのインスタンスを作成するたびに、これらのメソッドを選択して順番に呼ばなければならなくなります。
これではProductCとかProductDとかできるたびにmainがどんどん肥大化して、複雑になっていきますね。。。(´ε`;)

def ProductAInitializeA(product : Product) :
    # ProductAの初期化処理A

def ProductAInitializeB(product : Product) :
    # ProductAの初期化処理B

def ProductAInitializeC(product : Product) :
    # ProductAの初期化処理C

def ProductBInitializeA(product : Product) :
    # ProductBの初期化処理A

def ProductBInitializeB(product : Product) :
    # ProductBの初期化処理B

def ProductBInitializeC(product : Product) :
    # ProductBの初期化処理C

if __name__ == '__main__' :
    product = ProductA()
    ProductAInitializeA(product)
    ProductAInitializeB(product)
    ProductAInitializeC(product)
    product.functionA()  #これでProductAが使えるようになる

    product = ProductB()
    ProductBInitializeA(product)
    ProductBInitializeB(product)
    ProductBInitializeC(product)
    product.functionA()  #これでProductBが使えるようになる


これを回避するもっとも単純な方法は、ProductAクラス、ProductBクラスそれぞれに専用の初期化メソッドを実装し、コンストラクタなどで初期化処理を呼び出すことが考えられます。
こうするとmainはとてもシンプルになりますね(・`ω´・)

しかし、この方法ではProductAクラス、ProductBクラスに直接手を加えなければなりません。
もし「InitializeA、B、Cがこのアプリケーションのみで有効な初期化処理だった場合」 かつ 「ProductAクラスやProductBクラスを他のシステムで使いまわしたい場合」を考えると InitializeA、B、Cを他のシステムに合わせて変更しなければならなくなるかもしれません。
そうするとProductAクラス、ProductBクラスの再利用性は低下してしまいますね。。(;゚Д゚)

class ProductA(Product) :
    def functionA(self) :    
        # 処理A

    def __InitializeA(self) :
        # 初期化処理A

    def __InitializeB(self) :
        # 初期化処理B

    def __InitializeC(self) :
        # 初期化処理C

    def __init__(self) :
        self.__InitializeA()
        self.__InitializeB()
        self.__InitializeC()

class ProductB(Product) :
    def functionA(self) :    
        # 処理A

    def __InitializeA(self) :
        # 初期化処理A

    def __InitializeB(self) :
        # 初期化処理B

    def __InitializeC(self) :
        # 初期化処理C

    def __init__(self) :
        self.__InitializeA()
        self.__InitializeB()
        self.__InitializeC()

if __name__ == '__main__' :
    product = ProductA()
    product.functionA()

    product = ProductB()
    product.functionA()


そこで以下のようにしてみました(・`ω´・)b
この例では、まずFactoryという抽象クラスを用意しています。
このクラスには以下のものがあります。

  • 初期化処理を実装するためのInitializeA、B、Cインタフェース
  • Productのインスタンスを生成するためのcreateインスタンス
  • 初期化手順を実装したcreateProduct()メソッド

そしてFactoryAとFactoryBは、Factoryを継承して、それぞれ以下の実装が作れらています。

  • InitializeA、B、Cインタフェースを使ってProduct固有の初期化処理を実装
  • createインタフェースを使ってProductのインスタンスを生成・返却するように実装

これによりFactoryのcreateProductメソッドは、createでインスタンスを取得し、InitializeA、B、Cで初期化したインスタンスを返すことができるようになります。

これで、wikiにあるように「mainからProductの生成と初期化をFactoryクラスに追い出し」、そして「Productに手を加えないため再利用性を高めることができる」 ということが実現できそうな気がします。

class Factory(metaclass=ABCMeta) :
    @abstractmethod
    def _create(self) -> Product :
        pass
    
    @abstractmethod
    def _InitializeA(self, product : Product) :
        pass

    @abstractmethod
    def _InitializeB(self, product : Product) :
        pass

    @abstractmethod
    def _InitializeC(self, product : Product) :
        pass

    def createProduct(self) :
        product = self._create()
        self._InitializeA(product)
        self._InitializeB(product)
        self._InitializeC(product)
        return product

class FactoryA(Factory) :
    @abstractmethod
    def _create(self) -> Product :
        return FactoryA()
    
    @abstractmethod
    def _InitializeA(self, product : Product) :
        # 初期化処理A

    @abstractmethod
    def _InitializeB(self, product : Product) :
        # 初期化処理B

    @abstractmethod
    def _InitializeC(self, product : Product) :
        # 初期化処理C

class FactoryB(Factory) :
    @abstractmethod
    def _create(self) -> Product :
        return FactoryB()
    
    @abstractmethod
    def _InitializeA(self, product : Product) :
        # 初期化処理A

    @abstractmethod
    def _InitializeB(self, product : Product) :
        # 初期化処理B

    @abstractmethod
    def _InitializeC(self, product : Product) :
        # 初期化処理C

if __name__ == '__main__' :
    factory = FactoryA()
    product = factory.createProduct()
    product.functionA()

    factory = FactoryB()
    product = factory.createProduct()
    product.functionA()

こんな感じなのがFactoryMethodパターンかなと思っています\(^ω^)/

サンプルプログラム

以下、PythonFactoryMethodパターンのサンプルプログラムです。

# coding: utf-8

from abc import ABC, ABCMeta, abstractmethod

class Product(metaclass=ABCMeta) :
    @abstractmethod
    def use(self) -> None:
        pass

class Account(Product) :
    def __init__(self, owner: str) -> None :
        print("Create account: ", owner)
        self.__owner = owner

    def use(self) -> None:
        print("Use account: ", self.__owner)

    def getOwner(self) -> str :
        return self.__owner

class Factory(metaclass=ABCMeta) :
    def create(self, owner: str) -> Account :
        product = self.createProduct(owner)
        self.registerProduct(product)
        return product

    @abstractmethod
    def createProduct(self, owner: str) -> Account :
        pass

    @abstractmethod
    def registerProduct(self, product: Account) -> None :
        pass

class AccountFactory(Factory) :
    def __init__(self) :
        self.__owners: list[str] = []

    def createProduct(self, owner) -> Account :
        return Account(owner)

    def registerProduct(self, product: Account) -> None :
        self.__owners.append(product.getOwner())
    
    def getOwners(self) -> list :
        return self.__owners

if __name__ == '__main__' :
    factory = AccountFactory()

    account1 = factory.create("AAA")
    account2 = factory.create("BBB")
    account3 = factory.create("CCC")
    account4 = factory.create("DDD")

    account1.use()
    account2.use()
    account3.use()
    account4.use()

以下は実行結果です!

$ python FactoryMethod.py
Create account:  AAA
Create account:  BBB
Create account:  CCC
Create account:  DDD
Use account:  AAA
Use account:  BBB
Use account:  CCC
Use account:  DDD