- Pythonとデザインパターンの学習記録。今回はAbstract Factoryパターンについてメモする。
Abstract Factoryパターン とは
- インスタンスの生成を専門に行うクラスを用意し、整合性が必要とされるオブジェクト群を間違いなく生成するためのデザインパターン。
- 以下のような場合、オブジェクトの生成を担う専用のファクトリークラスを用意し、必要な属性を組み合わせる処理をファクトリークラスに実行させる。
- あるオブジェクト(例:エンドユーザー)は複数の属性(例:ID、名前、メールアドレス)から構成される。
- 属性はあるオブジェクトの構成要素に属するものとして定義される。
(例:エンドユーザーを定義するためのIDや名前はエンドユーザー用の構成要素に属する。サポートオペレーターを定義するためのIDや名前は別の構成要素に属する。)
Factory Method パターンとの違い
-
Factory Method パターン:
- 実装を持つクラスを作成するためのファクトリーを抽象メソッドで実現する。
- 生成対象のクラスが複数の場合、クラス内のファクトリー(factory method)が増えていく。
- 実装を持つクラスを作成するためのファクトリーを抽象メソッドで実現する。
-
Abstract Factory パターン:
- ファクトリー自体を他のクラスへ外部化する。
- ファクトリーを全て外部クラスに移し、それらのクラスに処理を委ねることでクラスの肥大化を防ぐ。
- ファクトリー自体を他のクラスへ外部化する。
サンプルプログラム
- ユーザー取得処理を行うAPIに適用する。
ディレクトリ構成
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
from api import app if __name__ == '__main__': app.run()
-
api/__init__.py
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コントローラ
- クエリパラメータの有無でユーザー生成方法を切り替える。
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
# 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
- 固定のユーザー情報を生成
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
- ユーザー情報をランダム生成
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
動作確認
-
ランダムユーザー情報生成(クエリパラメータ指定なし)
-
リクエスト
GET /api/sample HTTP/1.1 Host: localhost:5000
-
レスポンス
{ "email": "fsq8yjug2v@random.example.com", "id": "2xod95th7u", "name": "Sato Jiro" }
-
-
固定ユーザー情報生成(クエリパラメータ指定あり)
-
リクエスト
GET /api/sample?q=test HTTP/1.1 Host: localhost:5000
-
レスポンス
{ "email": "example@fixed.example.com", "id": "ABCDE12345", "name": "Yamada Taro" }
-