デザインパターンとは
デザインパターンは、ソフトウェア設計で繰り返し現れる問題に対する「定石の解決策」です。1994年にGoF(Gang of Four)が『Design Patterns』で23パターンを体系化しました。
パターンを知ることの価値は2つです。
- 実装の近道: 似た問題を解決済みの方法で素早く解ける
- 共通言語: 「ここはStrategyパターンにしよう」と一言で意図が伝わる
23パターンは生成・構造・振る舞いの3カテゴリに分類されます。ここでは実務頻出の5パターンを学びます。
1. Singleton(生成パターン)
インスタンスが1つしか存在しないことを保証する
class DatabaseConnection:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._connect()
return cls._instance
def _connect(self):
print("DB接続を確立")
self.connected = True
# どこから呼んでも同じインスタンス
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2) # True
使いどころ: 設定オブジェクト、ログマネージャ、DBコネクションプール
注意点: グローバル状態を持つため、テストが困難になりやすい。乱用禁止。
2. Factory Method(生成パターン)
オブジェクト生成をサブクラスに委譲する
from abc import ABC, abstractmethod
class Notification(ABC):
@abstractmethod
def send(self, message: str): ...
class EmailNotification(Notification):
def send(self, message):
print(f"メール送信: {message}")
class SlackNotification(Notification):
def send(self, message):
print(f"Slack通知: {message}")
class PushNotification(Notification):
def send(self, message):
print(f"プッシュ通知: {message}")
class NotificationFactory:
@staticmethod
def create(channel: str) -> Notification:
if channel == "email":
return EmailNotification()
elif channel == "slack":
return SlackNotification()
elif channel == "push":
return PushNotification()
raise ValueError(f"Unknown channel: {channel}")
# 使う側はどのクラスかを意識しない
notif = NotificationFactory.create("slack")
notif.send("デプロイが完了しました")
使いどころ: 設定によって生成するクラスを切り替えたいとき
3. Observer(振る舞いパターン)
状態変化を複数のオブジェクトに自動通知する(イベント駆動)
from abc import ABC, abstractmethod
class Observer(ABC):
@abstractmethod
def update(self, event: str, data: dict): ...
class EventEmitter:
def __init__(self):
self._listeners: dict[str, list[Observer]] = {}
def on(self, event: str, observer: Observer):
self._listeners.setdefault(event, []).append(observer)
def emit(self, event: str, data: dict = {}):
for observer in self._listeners.get(event, []):
observer.update(event, data)
# 具体的なオブザーバー
class OrderLogger(Observer):
def update(self, event, data):
print(f"[LOG] {event}: {data}")
class EmailNotifier(Observer):
def update(self, event, data):
if event == "order_placed":
print(f"注文確認メール送信 to {data['email']}")
class InventoryUpdater(Observer):
def update(self, event, data):
if event == "order_placed":
print(f"在庫を {data['product']} 分減らす")
# 使用例
emitter = EventEmitter()
emitter.on("order_placed", OrderLogger())
emitter.on("order_placed", EmailNotifier())
emitter.on("order_placed", InventoryUpdater())
emitter.emit("order_placed", {
"email": "user@example.com",
"product": "商品A",
})
# [LOG] order_placed: {...}
# 注文確認メール送信 to user@example.com
# 在庫を 商品A 分減らす
使いどころ: イベント処理、MVC のモデル変更通知、Webhook
4. Strategy(振る舞いパターン)
アルゴリズムをカプセル化して、実行時に切り替えられるようにする
from abc import ABC, abstractmethod
class SortStrategy(ABC):
@abstractmethod
def sort(self, data: list) -> list: ...
class BubbleSortStrategy(SortStrategy):
def sort(self, data):
arr = data.copy()
n = len(arr)
for i in range(n):
for j in range(n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr
class QuickSortStrategy(SortStrategy):
def sort(self, data):
if len(data) <= 1:
return data
pivot = data[len(data) // 2]
left = [x for x in data if x < pivot]
mid = [x for x in data if x == pivot]
right = [x for x in data if x > pivot]
return self.sort(left) + mid + self.sort(right)
class Sorter:
def __init__(self, strategy: SortStrategy):
self._strategy = strategy
def set_strategy(self, strategy: SortStrategy):
self._strategy = strategy
def sort(self, data: list) -> list:
return self._strategy.sort(data)
# 実行時にアルゴリズムを切り替え
sorter = Sorter(BubbleSortStrategy())
print(sorter.sort([5, 2, 8, 1])) # バブルソート
sorter.set_strategy(QuickSortStrategy())
print(sorter.sort([5, 2, 8, 1])) # クイックソートに切り替え
使いどころ: 決済方法の切り替え、圧縮アルゴリズム、バリデーションロジック
5. Decorator(構造パターン)
既存クラスを修正せずに機能を追加する
from abc import ABC, abstractmethod
from functools import wraps
# Python の関数デコレータとしての実装
def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"呼び出し: {func.__name__}({args}, {kwargs})")
result = func(*args, **kwargs)
print(f"結果: {result}")
return result
return wrapper
def measure_time(func):
import time
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
print(f"実行時間: {elapsed:.4f}秒")
return result
return wrapper
@log_calls
@measure_time
def fetch_data(url: str):
import time
time.sleep(0.1) # API呼び出しシミュレーション
return {"data": "..."}
fetch_data("https://api.example.com/users")
# 呼び出し: fetch_data(('https://...',), {})
# 実行時間: 0.1001秒
# 結果: {'data': '...'}
クラスベースの実装も可能です。
class TextProcessor(ABC):
@abstractmethod
def process(self, text: str) -> str: ...
class PlainText(TextProcessor):
def process(self, text):
return text
class UpperCaseDecorator(TextProcessor):
def __init__(self, processor: TextProcessor):
self._processor = processor
def process(self, text):
return self._processor.process(text).upper()
class TrimDecorator(TextProcessor):
def __init__(self, processor: TextProcessor):
self._processor = processor
def process(self, text):
return self._processor.process(text).strip()
# 自由に組み合わせ
processor = UpperCaseDecorator(TrimDecorator(PlainText()))
print(processor.process(" hello world ")) # "HELLO WORLD"
使いどころ: ミドルウェア、ロギング・キャッシュの後付け、入力バリデーション
パターン選択の指針
オブジェクト生成を柔軟にしたい
→ Factory Method / Abstract Factory / Builder
インスタンスを1つに制限したい
→ Singleton(乱用注意)
アルゴリズムを切り替えたい
→ Strategy
イベント/変更を複数に通知したい
→ Observer
機能を動的に追加したい
→ Decorator
複雑なオブジェクトを段階的に構築したい
→ Builder
既存コードへのインターフェースを合わせたい
→ Adapter
オブジェクトのツリー構造を扱いたい
→ Composite
まとめ
デザインパターンは「こう実装せよ」という命令ではなく、問題と解決策の共通語彙です。
- パターン名を会話に使うと意思疎通が速くなる
- パターンを当てはめることが目的化しないよう注意
- まず「何が問題か」を明確にしてから、パターンを手段として選ぶ