GoFデザインパターンを復習中。
前回は構造パターンの4つ(Adapter, Bridge, Composite, Decorator)を掘り下げたので、今回は残りの3つ(Facade, Flyweight, Proxy)を掘り下げ
在庫確認→決済処理→口座入金をまとめて「決済ボタン」を窓口にさせる
from __future__ import annotations
# サブシステム窓口クラス
class Facade:
def __init__(self, subsystem1: Subsystem1, subsystem2: Subsystem2, subsystem3: Subsystem3) -> None:
self._subsystem1 = subsystem1 or Subsystem1()
self._subsystem2 = subsystem2 or Subsystem2()
self._subsystem3 = subsystem3 or Subsystem3()
def operation(self) -> str:
results = []
results.append("facade管理:サブシステムを初期化")
results.append(self._subsystem1.operation1())
results.append(self._subsystem2.operation1())
results.append(self._subsystem3.operation1())
results.append("facade管理:サブシステム実行")
results.append(self._subsystem1.operation_n())
results.append(self._subsystem2.operation_z())
results.append(self._subsystem3.operation_z())
return "\n".join(results)
# 在庫管理クラス
class Subsystem1:
def operation1(self) -> str:
return "在庫確認:準備"
def operation_n(self) -> str:
return "在庫確認:実行"
# 決済処理クラス
class Subsystem2:
def operation1(self) -> str:
return "決済処理:準備"
def operation_z(self) -> str:
return "決済処理:実行"
# 口座入金クラス
class Subsystem3:
def operation1(self) -> str:
return "口座入金:準備"
def operation_z(self) -> str:
return "口座入金:実行"
# 「決済ボタン」
def client_code(facade: Facade) -> None:
print(facade.operation(), end="")
if __name__ == "__main__":
subsystem1 = Subsystem1() # 在庫管理サブシステムのインスタンス
subsystem2 = Subsystem2() # 決済処理サブシステムのインスタンス
subsystem3 = Subsystem3() # 口座入金サブシステムのインスタンス
facade = Facade(subsystem1, subsystem2, subsystem3) # facadeクラスでサブシステムのインターフェース管理
print("---------------------------------------")
client_code(facade) # クライアントが「決済ボタン」を実行
---------------------------------------
facade管理:サブシステムを初期化
在庫確認:準備
決済処理:準備
入金処理:準備
facade管理:サブシステム実行
在庫確認:実行
決済処理:実行
入金処理:実行
とあるゲームでプレイヤーがキャラクターオブジェクトを自由に画面に配置できる機能
import json
from typing import Dict
# 共通部分の組み込みクラス
class Flyweight():
def __init__(self, shared_state: str) -> None:
self._shared_state = shared_state
def operation(self, unique_state: str) -> None:
s = json.dumps(self._shared_state, ensure_ascii=False)
u = json.dumps(unique_state, ensure_ascii=False)
print(f"【Flyweight作成】: 共有プロパティ ({s}) |ユニークプロパティ ({u})", end="")
# Flyweightオブジェクトの作成・管理クラス
class FlyweightFactory():
_flyweights: Dict[str, Flyweight] = {}
def __init__(self, initial_flyweights: Dict) -> None:
for state in initial_flyweights:
self._flyweights[self.get_key(state)] = Flyweight(state)
# 追加オブジェクトのキー情報チェック
def get_key(self, state: Dict) -> str:
return "_".join(sorted(state))
# 追加オブジェクトの作成or複製実行
def get_flyweight(self, shared_state: Dict) -> Flyweight:
key = self.get_key(shared_state)
if not self._flyweights.get(key):
print("=====================")
print("【Flyweight管理】:同オブジェクトが見つからないため、新規作成")
self._flyweights[key] = Flyweight(shared_state)
print("=====================")
else:
print("=====================")
print("【Flyweight管理】:既存オブジェクトのため、再利用")
print("=====================")
return self._flyweights[key]
# 現在の総キャラ数チェック
def list_flyweights(self) -> None:
count = len(self._flyweights)
print(f"現在、{count}個のFlyweightオブジェクトを所持")
print("\n".join(map(str, self._flyweights.keys())), end="")
# クライアントの追加操作
def add_create_obj(
factory: FlyweightFactory, plates: str, owner: str,
brand: str, model: str, color: str
) -> None:
print("\n\n【クライアント操作】:オブジェクトを追加リクエスト")
flyweight = factory.get_flyweight([brand, model, color])
flyweight.operation([plates, owner])
if __name__ == "__main__":
# 画面に初期配置させているキャラクターオブジェクト
factory = FlyweightFactory([
["通行人", "男", "成人"],
["通行人", "男", "子供"],
["店員", "女", "成人"],
["客", "男", "老人"],
["客", "女", "成人"],
])
お客さんの行動パターンの老人男性、名前「山田太郎」さん(プレイヤー操作可能)を追加
# 新規オブジェクトをゲーム画面に追加
add_create_obj(factory, "true", "山田太郎", "客", "男", "老人")
# Flyweightオブジェクト一覧
factory.list_flyweights()
【クライアント操作】:オブジェクトを追加リクエスト
【Flyweight管理】:既存オブジェクトのため、再利用
【Flyweight作成】: 共有プロパティ (["客", "男", "老人"]) |ユニークプロパティ (["true", "山田太郎"])
--------------------------------------
現在、5個のFlyweightオブジェクトを所持
成人_男_通行人
子供_男_通行人
女_店員_成人
客_男_老人
女_客_成人
--------------------------------------
さらに通行人の行動パターンの成人女性、名前「佐藤花子」さん(プレイヤー操作不可)を追加
# 新規オブジェクトをゲーム画面に追加
add_create_obj(factory, "false", "佐藤花子", "通行人", "女", "成人")
# Flyweightオブジェクト一覧
factory.list_flyweights()
【クライアント操作】:オブジェクトを追加リクエスト
【Flyweight管理】:同オブジェクトが見つからないため、新規作成
【Flyweight作成】: 共有プロパティ (["通行人", "女", "成人"]) |ユニークプロパティ (["false", "佐藤花子"])
--------------------------------------
現在、6個のFlyweightオブジェクトを所持
成人_男_通行人
子供_男_通行人
女_店員_成人
客_男_老人
女_客_成人
女_成人_通行人
--------------------------------------
リクエスト処理前にプロキシクラスで身元確認&ログ保存を実装
from abc import ABC, abstractmethod
# 主体監視オブジェクトクラス
class Subject(ABC):
@abstractmethod
def request(self) -> None:
pass
# 実際の主体オブジェクトクラス
class RealSubject(Subject):
def request(self) -> None:
print("実態オブジェクト:リクエストを処理")
# 代理プロキシクラス
class Proxy(Subject):
def __init__(self, real_subject: RealSubject) -> None:
self._real_subject = real_subject
# リクエスト処理
def request(self) -> None:
if self.check_access():
self._real_subject.request()
self.log_access()
# アクセス確認
def check_access(self) -> bool:
print("代理:実行する前にアクセス元情報をチェック")
return True
# アクセスログ
def log_access(self) -> None:
print("代理:アクセス時間をログに保存", end="")
# クライアントの実行クラス
def client_code(subject: Subject) -> None:
subject.request()
if __name__ == "__main__":
print("【クライアント:実際のコードで実行】")
real_subject = RealSubject()
client_code(real_subject)
print("-------------------------------------------------")
print("【クライアント:プロキシを使用して同じクライアントコードを実行】")
proxy = Proxy(real_subject)
client_code(proxy)
【クライアント:実際のコードで実行】
実態オブジェクト:リクエストを処理
-------------------------------------------------
【クライアント:プロキシを使用して同じクライアントコードを実行】
代理:実行する前にアクセス元情報をチェック
実態オブジェクト:リクエストを処理
代理:アクセス時間をログに保存
パターン名 | 採用シチュエーション |
Adapter | サードパーティーAPIなどの内部と互換性のないクラスとオブジェクトを何とかしたい |
Bridge | 機能側と実装側の組み合わせパターンが複数あるクラスとオブジェクト間の関係を何とかしたい |
Composite | 限定的な範囲を対象に大小関わらず再帰処理が必要なモノを何とかしたい |
Decorator | 既に完成された(または変更ができない)クラスから実行されたオブジェクトを何とかしたい |
Facade | 複数のサブシステムをまたがるショートカットまたはインターフェースが別途欲しい |
Flyweight | ボトルネックになるモノを何とかしたい |
Proxy | 実態クラスの実行前後に色々したい or 集中的な処理をコントロールしたい |
Decorator以降は構造パターンという”手法”よりも使い所がかなり具体的なデコレータやラッパー的な”手段”に近いなという印象
実装先がWEBアプリの場合、この構造パターンのほとんどがFWのデフォ機能に内包されていると思うので、ATMや自動改札のような実装形態が予め完成されてない業務システム開発に柔軟なJAVAでの開発の際の先人の知恵であって、この手法を無理やりに現在のWEB系に導入させようとすると色んな無駄と混乱の巻き込み事故になりかねないと思われる。