pythonを使ってファクトリメソッドパターンを実装する方法について
python を使ってファクトリメソッドパターンの実装をした際に試した内容です。
ファクトリメソッドパターンの作り方としては二通り知っていますが、そのうちの「カテゴリによって実行するインスタンスを決める」方法の実装を紹介します。
今回はデコレータを使ったパターンと、使わなかったときのパターンを比較して、どちらを選択したほうが良いか考えます。
結論としては、デコレータを使ったパターンの方がコード量が少なく、メンテ時の修正範囲も少なくなると思いました。
環境について
- python3.9.0
題材について
以下の条件を満たすクラス設計をします。
- 従業員が存在する
- 従業員は2つのカテゴリで分類される
- エンジニア
- 営業
- 各従業員には名前がある
- 各従業員はカテゴリ別に通勤方法が決まっている
- エンジニアは徒歩で通勤する
- 営業は電車で通勤する
- 従業員の名前と通勤方法を出力できる
デコレータを使ったパターン
デコレータを使うことで、どのクラスがファクトリーの対象となっているのかがわかりやすくなっています。
from enum import Enum class Employee: def __init__(self, name): self._name = name def commute_method(self): # 本来は出社方法を集約かコンポジションで管理するのが良いです。 # 簡易的な実装にするために関数を実装するようにします。 # もしくは、出社するためのクラスを別に用意するのが良いと思います。 raise NotImplementedError @property def name(self): return self._name class CategoryTypes: engineer = 'engineer' sales = 'sales' class EmployeeFactory: class_ = {} @classmethod def register(cls, category): def wrapper(cls_): if not issubclass(cls_, Employee): raise TypeError cls.class_[category] = cls_ return cls_ return wrapper @classmethod def create(cls, category): cls_ = cls.class_[category] return cls_ @EmployeeFactory.register(CategoryTypes.engineer) class Engineer(Employee): def commute_method(self): return '歩いて出社' @EmployeeFactory.register(CategoryTypes.sales) class Sales(Employee): def commute_method(self): return '電車で出社' class EmployeeInfo: def __init__(self, employee_): self._employee = employee_ def print(self): print('\n'.join([ f'名前: {self._employee.name}', f'通勤方法: {self._employee.commute_method()}', ])) if __name__ == '__main__': employee = EmployeeFactory.create(CategoryTypes.engineer) sales = EmployeeFactory.create(CategoryTypes.sales) EmployeeInfo(employee('Aさん')).print() EmployeeInfo(sales('Bさん')).print()
実行結果
名前: Aさん 通勤方法: 歩いて出社 名前: Bさん 通勤方法: 電車で出社
デコレータを使わない例
デコレータを使った例を基に、デコレータを使わなかったときのコードを考えてみます。
from enum import Enum class Employee: def __init__(self, name): self._name = name def commute_method(self): # 本来は出社方法を集約かコンポジションで管理するのが良いです。 # 簡易的な実装にするために関数を実装するようにします。 # もしくは、出社するためのクラスを別に用意するのが良いと思います。 raise NotImplementedError @property def name(self): return self._name class CategoryTypes(Enum): engineer = 'engineer' sales = 'sales' class Engineer(Employee): def commute_method(self): return '歩いて出社' class Sales(Employee): def commute_method(self): return '電車で出社' class EmployeeFactory: FACTORY_MAP = { CategoryTypes.engineer: Engineer, CategoryTypes.sales: Sales, } @classmethod def create(cls, category_type): if category_type in cls.FACTORY_MAP: return cls.FACTORY_MAP[category_type] raise TypeError class EmployeeInfo: def __init__(self, employee_): self._employee = employee_ def print(self): print('\n'.join([ f'名前: {self._employee.name}', f'通勤方法: {self._employee.commute_method()}', ])) if __name__ == '__main__': employee = EmployeeFactory.create(CategoryTypes.engineer) sales = EmployeeFactory.create(CategoryTypes.sales) EmployeeInfo(employee('Aさん')).print() EmployeeInfo(sales('Bさん')).print()
デコレータを使った場合と異なるのは、ファクトリを作成するクラス(EmployeeFactory
)です。
また、カテゴリを管理するクラスがEnum
になっています。
なぜEnum
になっているのかというと、デコレータを使った場合の実装だと、Enum.field.value
のようにvalue
を書く必要があったからです。
value
を書くのを忘れてエラーになってしまうことが多いため、あえてEnum
での実装を避けています。
class CategoryTypes(Enum): engineer = 'engineer' sales = 'sales' class EmployeeFactory: FACTORY_MAP = { CategoryTypes.engineer: Engineer, CategoryTypes.sales: Sales, } @classmethod def create(cls, category_type): if category_type in cls.FACTORY_MAP: return cls.FACTORY_MAP[category_type] raise TypeError
比較する
デコレータを使った場合と、デコレータを使わなかった場合を比較すると、デコレータを使った場合の方がカテゴリの追加時(更新時)に修正範囲が小さいです。
例えば、新しく「カスタマーサポート」を追加する際を考えてみます。
デコレータを使った実装の場合は以下の修正が必要になります。
class CategoryTypes: engineer = 'engineer' sales = 'sales' customer_support = 'customer_support' @EmployeeFactory.register(CategoryTypes.customer_support) class CustomerSupport(Employee): def commute_method(self): return '自転車で出社' customer_support = EmployeeFactory.create(CategoryTypes.customer_support) EmployeeInfo(customer_support('Cさん')).print()
このように、カテゴリを追加して、そのクラスを用意するだけで修正が完了します。
デコレータを使わない場合についても同様に考えてみます。
class CategoryTypes(Enum): engineer = 'engineer' sales = 'sales' customer_support = 'customer_support' class CustomerSupport(Employee): def commute_method(self): return '自転車で出社' class EmployeeFactory: FACTORY_MAP = { CategoryTypes.engineer: Engineer, CategoryTypes.sales: Sales, CategoryTypes.customer_support: CustomerSupport, } # 略 customer_support = EmployeeFactory.create(CategoryTypes.customer_support) EmployeeInfo(customer_support('Cさん')).print()
このように、FACTORY_MAP
を修正するひと手間が必要になってしまいます。
今回はとてもシンプルな例で比較したため、1箇所のみの差しかありませんでしたが複雑になれば大きな差になるため気をつけたほうが良いです。
結論
ファクトリメソッドパターンを python で実装する場合は、デコレータを積極的に使うことで、修正箇所が少なくなります。