Abstract Factoryパターン Python実装 メモ

* Pythonとデザインパターンの学習記録。今回はAbstract Factoryパターンについてメモする。

## Abstract Factoryパターン とは

* インスタンスの生成を専門に行うクラスを用意し、整合性が必要とされるオブジェクト群を間違いなく生成するためのデザインパターン。
* 以下のような場合、オブジェクトの生成を担う専用のファクトリークラスを用意し、必要な属性を組み合わせる処理をファクトリークラスに実行させる。
  * あるオブジェクト（例：エンドユーザー）は複数の属性（例：ID、名前、メールアドレス）から構成される。
  * 属性はあるオブジェクトの構成要素に属するものとして定義される。
    （例：エンドユーザーを定義するためのIDや名前はエンドユーザー用の構成要素に属する。サポートオペレーターを定義するためのIDや名前は別の構成要素に属する。）

### Factory Method パターンとの違い

* Factory Method パターン：
  * 実装を持つクラスを作成するためのファクトリーを抽象メソッドで実現する。
    * 生成対象のクラスが複数の場合、クラス内のファクトリー(factory method)が増えていく。

* Abstract Factory パターン：
  * ファクトリー自体を他のクラスへ外部化する。
    * ファクトリーを全て外部クラスに移し、それらのクラスに処理を委ねることでクラスの肥大化を防ぐ。

## サンプルプログラム

* ユーザー取得処理を行うAPIに適用する。
  * クエリパラメータの有無でユーザー生成処理(固定 or ランダム)を切り替える。
     * 固定ユーザー生成:`FixedUserFactory`、`FixedID`、`FixedName`、`FixedEmail`
     * ランダムユーザー生成:`RandomUserFactory`、`RandomID`、`RandomName`、`RandomEmail`

    ![abstract_factory_code.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586535/f59fa6ac-6357-75c3-977f-f694c3f16775.png)


### ディレクトリ構成

```
Project
│  app.py
L_ api
   │  __init__.py
   │
   ├─views
     │  sample.py
     │  __init__.py
     │
	 ├─factory
  		│  factory.py
  		│  __init__.py
  		│
  		├─fixeduserfactory
  		│  L_  fixed_user_factory.py
  		│  L_  __init__.py
  		│
  		├─randomuserfactory
  		  L_  random_user_factory.py
  		  L_  __init__.py
  		
```

### 実装

* `app.py`

  ```python
  from api import app
  
  if __name__ == '__main__':
      app.run()
  
  ```

  

* `api/__init__.py`

  ```python
  from flask import Flask
  from .views.sample import sample_router
  
  
  def create_app():
  
      app = Flask(__name__)
  
      app.register_blueprint(sample_router, url_prefix='/api')
  
      return app
  
  
  app = create_app()
  
  ```

* `api/views/sample.py`

  * APIコントローラ
  * クエリパラメータの有無でユーザー生成方法を切り替える。

  ```python
  from flask import Flask, Blueprint, request
  import json
  from .factory.factory import AbstractUserFactory
  from .factory.fixeduserfactory.fixed_user_factory import FixedUserFactory
  from .factory.randomuserfactory.random_user_factory import RandomUserFactory
  
  # Routing Settings
  sample_router = Blueprint('sample_router', __name__)
  
  app = Flask(__name__)
  
  
  @sample_router.route("/sample",  methods=['GET'])
  def get():
  
      query = request.args.get('q')
      user = {}
      if query is not None:
          f = AbstractUserFactory(FixedUserFactory())
          user = f.make_user("@fixed.example.com")
      else:
          f = AbstractUserFactory(RandomUserFactory())
          user = f.make_user("@random.example.com")
  
      return json.loads(json.dumps(user))
  ```

* **`api/views/factory/factory.py`**

  * AbstractFactoryとAbstractProduct

  ```python
  # AbstractFactory
  class AbstractUserFactory:
      def __init__(self, user_factory):
          self.factroy = user_factory
  
      def make_user(self, domain):
          self.user = {}
          self.user["id"] = self.factroy.get_id()
          self.user["name"] = self.factroy.get_name()
          self.user["email"] = self.factroy.get_email(domain)
          return self.user
  
      # createproduct
      def get_id(self):
          pass
  
      # createproduct
      def get_name(self):
          pass
      
  	# createproduct
      def get_email(self, domain="@example.com"):
          pass
  
  # AbstractProduct
  class ID:
      def generate(self):
          pass
  
  
  class Name:
      def generate(self):
          pass
  
  
  class Email:
      def __init__(self, domain):
          self.domain = domain
  
      def generate(self):
          pass
  
  ```

  

* **`api/views/factory/fixeduserfactory/fixed_user_factory.py`**

  * Concrete FactoryとConcrete Product
  * 固定のユーザー情報を生成

  ```python
  from api.views.factory.factory import AbstractUserFactory, ID, Name, Email
  
  # ConcreteFactory
  class FixedUserFactory(AbstractUserFactory):
      def __init__(self):
          pass
  
      # createproduct
      def get_id(self):
          return FixedID().generate()
  
      # createproduct
      def get_name(self):
          return FixedName().generate()
  
      # createproduct
      def get_email(self, domain):
          return FixedEmail(domain).generate()
  
  
  # ConcreteProduct
  class FixedID(ID):
      def generate(self):
          id = "ABCDE12345"
          print("固定 ID:{0} を生成しました。".format(id))
          return id
  
  
  # ConcreteProduct
  class FixedName(Name):
      def generate(self):
          name = "Yamada Taro"
          print("固定 Name:{0} を生成しました。".format(name))
          return name
  
  
  # ConcreteProduct
  class FixedEmail(Email):
      def generate(self):
          email = "example"+self.domain
          print("固定 Email:{0} を生成しました。".format(email))
          return email
  
  ```


* **`api/views/factory/randomuserfactory/random_user_factory.py`**

  * Concrete FactoryとConcrete Product
  * ユーザー情報をランダム生成

  ```python
  from api.views.factory.factory import AbstractUserFactory, ID, Name, Email
  import random
  
  
  # ConcreteFactory
  class RandomUserFactory(AbstractUserFactory):
      def __init__(self):
          pass
  
      # createproduct
      def get_id(self):
          return RandomID().generate()
  
      # createproduct
      def get_name(self):
          return RandomName().generate()
  
      # createproduct
      def get_email(self, domain):
          return RandomEmail(domain).generate()
  
  
  # ConcreteProduct
  class RandomID(ID):
      def generate(self):
          chars = '0123456789abcdefghijklmnopqrstuvwxyz'
          id = ''.join([random.choice(chars) for _ in range(10)])
          print("ランダム ID:{0} を生成しました。".format(id))
          return id
  
  
  # ConcreteProduct
  class RandomName(Name):
      def generate(self):
          num = random.randrange(3)
          name_list = ["Yamada Taro", "Sato Jiro", "Tanaka Saburo"]
  
          name = name_list[num]
          print("ランダム Name:{0} を生成しました。".format(name))
          return name
  
  
  # ConcreteProduct
  class RandomEmail(Email):
      def generate(self):
          chars = '0123456789abcdefghijklmnopqrstuvwxyz'
          local_part = ''.join([random.choice(chars) for _ in range(10)])
          email = local_part + self.domain
          print("ランダム Email:{0} を生成しました。".format(email))
          return email
  ```

  

### 動作確認

* ランダムユーザー情報生成(クエリパラメータ指定なし)

  * リクエスト

    ```http
    GET /api/sample HTTP/1.1
    Host: localhost:5000
    ```

  * レスポンス

    ```json
    {
        "email": "fsq8yjug2v@random.example.com",
        "id": "2xod95th7u",
        "name": "Sato Jiro"
    }
    ```

    

* 固定ユーザー情報生成(クエリパラメータ指定あり)

  * リクエスト

    ```http
    GET /api/sample?q=test HTTP/1.1
    Host: localhost:5000
    ```

  * レスポンス

    ```json
    {
        "email": "example@fixed.example.com",
        "id": "ABCDE12345",
        "name": "Yamada Taro"
    }
    ```

    

## 参考情報

* [Abstract Factoryパターン](https://pydp.info/GoF_dp/creation/01_Abstract_Factory/)
* [Pythonで、デザインパターン「Abstract Factory」を学ぶ](https://qiita.com/ttsubo/items/30c86a265430a9e5050a)
* [『実践 Python 3』で学ぶデザインパターン : Abstract Factoryパターン](https://dackdive.hateblo.jp/entry/python-design-pattern/abstract-factory)
* [Factory MethodとAbstract Factoryの違いを考える](http://cyberboy6.blog.fc2.com/blog-entry-432.html)