Factory Methodパターン Python実装 メモ



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

## Factory Methodパターン とは

* インスタンスの生成方法を親クラスで定義し、具体的な処理を子クラスで行う、デザインパターンの一つ。
* 実際のインスタンス生成を子クラスに行わせることで、生成するインスタンスを柔軟に選択できるようにする。
  * 生成するインスタンスごとにファクトリ(下記Creator)を用意する。

### クラス図と用語

![factory_method.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586535/147968f0-ab93-d771-a01f-ad037418d3cb.png)


* 抽象クラス
  * Creator:インスタンス生成クラス
  * Product:生成対象オブジェクト
* 具象クラス：抽象クラスに対応するCreatorとProduct



## サンプルプログラム

* ID生成処理を行うAPIに適用する。

  * クエリパラメータの有無でID生成処理(複数文字種 or 1文字種)を切り替える。

    ![factory_method_code.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586535/e330aedc-84de-155e-0734-1b88d3b96118.png)


### ディレクトリ構成

```
Project
│  app.py
L_ api
   │  __init__.py
   │
   ├─views
     │  sample.py
     │  __init__.py
     │
     L_ factorymethod
      	 │  factory.py
      	 L_  __init__.py
    	 │
         L_ generatorfactory
			 L_  one_char_id_generator_factory.py
			 L_  random_id_generator_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コントローラ

* クエリパラメータの有無でID生成方法を切り替える。

  ```python
  from flask import Flask, Blueprint, request
  import json
  from .factorymethod.generatorfactory.one_char_id_generator_factory import OneCharIDGenratorFactory
  from .factorymethod.generatorfactory.random_id_generator_factory import RandomIDGenratorFactory
  
  # Routing Settings
  sample_router = Blueprint('sample_router', __name__)
  
  app = Flask(__name__)
  
  
  def generateID(factoryObject, length):
      generator = factoryObject.create(length)
      return generator.generate()
  
  
  @sample_router.route("/sample",  methods=['GET'])
  def get():
  
      query = request.args.get('q')
      id = ""
      if query is not None:
          id = generateID(OneCharIDGenratorFactory(), 5)
      else:
          id = generateID(RandomIDGenratorFactory(), 5)
  
      res_body = {
          "id": id
      }
      return json.loads(json.dumps(res_body))
  ```

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

  * 抽象Creator、Product 定義

  ```python
  from abc import ABCMeta, abstractmethod
  
  # Creator
  class Factory(metaclass=ABCMeta):
      def create(self, length):
          p = self.createGenerator(length)
          return p
  
      @abstractmethod
      def createGenerator(self, length):
          pass
  
  # Product
  class Generator(metaclass=ABCMeta):
      @abstractmethod
      def generate(self, length):
          pass
  ```

  

* **`api/views/factorymethod/one_char_id_generator_factory.py`**

  * 具象Creator/Product
  * 文字種１のランダムIDを生成

  ```python
  from api.views.factorymethod.factory import Factory, Generator
  import random
  
  
  class OneCharIDGenratorFactory(Factory):
      def __init__(self):
          self.length = 0
  
      def createGenerator(self, length):
          return OneCharIDGenerator(length)
  
  
  class OneCharIDGenerator(Generator):
      def __init__(self, length):
          self.length = length
          print("長さ:{0} の1文字種 IDを生成します。".format(self.length))
  
      def generate(self):
          chars = '0123456789abcdefghijklmnopqrstuvwxyz'
          v = random.choice(chars)
          id = ''.join([v for _ in range(self.length)])
          print("ID:{0} を生成しました。".format(id))
          return id
  
  ```


* **`api/views/factorymethod/random_id_generator_factory.py`**

  * 具象Creator/Product
  * 複数文字種のランダムIDを生成

  ```python
  from api.views.factorymethod.factory import Factory, Generator
  import random
  
  
  class RandomIDGenratorFactory(Factory):
      def __init__(self):
          self.length = 0
  
      def createGenerator(self, length):
          return RandomIDGenerator(length)
  
  
  class RandomIDGenerator(Generator):
      def __init__(self, length):
          self.length = length
          print("長さ:{0} のランダムIDを生成します。".format(self.length))
  
      def generate(self):
          chars = '0123456789abcdefghijklmnopqrstuvwxyz'
          id = ''.join([random.choice(chars) for _ in range(self.length)])
          print("ID:{0} を生成しました。".format(id))
          return id
  ```

  

### 動作確認

* 複数文字種のランダムID生成(クエリパラメータ指定なし)

  * リクエスト

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

  * レスポンス

    ```json
    {
        "id": "1arh3j2jba8x2fix"
    }
    ```

    

* 1文字種のランダムID生成(クエリパラメータ指定あり)

  * リクエスト

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

  * レスポンス

    ```json
    {
        "id": "nnnnn"
    }
    ```

    

## 参考情報

* [Factory Method パターン](https://ja.wikipedia.org/wiki/Factory_Method_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3)
* [Pythonで、デザインパターン「Factory Method」を学ぶ](https://qiita.com/ttsubo/items/623f32f9e96cb16d04a4)