Strategyパターン Python実装 メモ

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

## Strategy パターンとは

* 「戦略」(アルゴリズムなど)の切り替えや追加を簡単に行うためのデザインパターン。
* 戦略処理を別クラスとして作成することで、戦略を変更したい場合に、利用する戦略クラスを変更するという方法で対応する。
* メソッド中にアルゴリズムをべた書きするよりも柔軟でメンテナンスしやすい設計となる。

### クラス図と用語

![strategy_pattern.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586535/ed7cf87f-ee15-9d7d-fde9-6d07415d5e51.png)

* Strategy：特定の処理を抽象化したクラス(抽象戦略)
* ConcreteStrategy : 具体的な処理を行うクラス(具体戦略)
* Context：Strategyの利用者



## サンプルプログラム

* ID生成処理を行うAPIに適用する。
  * クエリパラメータの有無でID生成用Strategyを切り替える。

### ディレクトリ構成

```
Project
│  app.py
│
│
L_ api
   │  __init__.py
   │
   ├─views
     │  sample.py
     │  __init__.py
     │
     L_ strategy
           L_  IDgenerator.py
           L_  IDStrategy.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 .strategy.IDStrategy import IDStrategy
  from .strategy.IDStrategy import FixedIDStrategy, RandomIDStrategy
  from .strategy.IDgenerator import IDgenerator
  
  # Routing Settings
  sample_router = Blueprint('sample_router', __name__)
  
  app = Flask(__name__)
  
  
  @sample_router.route("/sample",  methods=['GET'])
  def get():
  
      query = request.args.get('q')
      id = ""
      # クエリパラメータ未指定の場合、固定のIDを取得する
      if query is not None:
          generator = IDgenerator(FixedIDStrategy())
          id = generator.generateID()
      # クエリパラメータ指定ありの場合、ランダムのIDを取得する
      else:
          generator = IDgenerator(RandomIDStrategy())
          id = generator.generateID()
  
      res_body = {
          "id": id
      }
      return json.loads(json.dumps(res_body))
  
  ```

* `api/views/strategy/IDStrategy.py`

  * Strategy：抽象、具体戦略(ランダム、固定ID生成)定義

  ```python
  import random
  
  # ID生成処理を抽象化したクラス(抽象戦略 Strategy)
  class IDStrategy:
      def generateID(self):
          raise 'Abstract Method Called'
  
  
  # ランダム生成したIDを返却するクラス(具体戦略 ConcreteStrategy)
  class RandomIDStrategy(IDStrategy):
      def generateID(self):
          chars = '0123456789abcdefghijklmnopqrstuvwxyz'
          return ''.join([random.choice(chars) for _ in range(16)])
  
  
  # 固定IDを返却するクラス(具体戦略 ConcreteStrategy)
  class FixedIDStrategy(IDStrategy):
      def generateID(self):
          return '0123abcd4567efgh'
  
  ```

  

* `api/views/strategy/IDgenerator.py`

  * Context:ID生成Strategyを呼び出す。

  ```python
  # コンテキスト(Context)：ストラテジーの利用者
  class IDgenerator:
      def __init__(self, IDStrategy):
          self.IDStrategy = IDStrategy
  
      def generateID(self):
          return self.IDStrategy.generateID()
  
  ```

  

### 動作確認

* ランダムID生成戦略の呼び出し(クエリパラメータ指定なし)

  * リクエスト

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

  * レスポンス

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

    

* 固定ID生成戦略の呼び出し(クエリパラメータ指定あり)

  * リクエスト

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

  * レスポンス

    ```json
    {
        "id": "0123abcd4567efgh"
    }
    ```

    

## 参考情報

* [Strategyパターン](https://pydp.info/GoF_dp/behavior/21_Strategy/index.html)
* [Pythonでデザインパターン　Strategyパターン](https://qiita.com/kotetsu75/items/186af7006b44703c0379)