目次
「デザインパターンは設計の共通言語」——これはソフトウェアエンジニアの世界で広く信じられている教訓だ。GoF(Gang of Four)が 1994 年に提唱した 23 のパターンは、30 年経った今でも有効だ。本稿では主要なパターンを現代的な実装例とともに解説する。
デザインパターンとは
デザインパターンは**「頻出する設計問題に対する、汎用的な解決策のテンプレート」**だ。
| パターンの利点 | 説明 |
|---|---|
| 共通語彙 | 「Strategy パターンで」と言えば設計が伝わる |
| 再利用 | 検証済みの解決策をそのまま適用 |
| 保守性 | 変更の影響範囲を局所化 |
1. Singleton(シングルトン)——インスタンスを 1 つだけ
1.1 目的
クラスのインスタンスが 1 つだけ存在することを保証する。
1.2 実装(Python)
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
if not hasattr(self, 'initialized'): # 再初期化防止
self.data = {}
self.initialized = True
# 使用例
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # True(同じインスタンス)
1.3 実装(TypeScript)
class Singleton {
private static instance: Singleton | null = null;
public data: Map<string, any> = new Map();
private constructor() {}
public static getInstance(): Singleton {
if (Singleton.instance === null) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
}
// 使用例
const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
console.log(s1 === s2); // true
1.4 使用例:設定管理
class ConfigManager(Singleton):
def __init__(self):
super().__init__()
self.config = self._load_config()
def _load_config(self) -> dict:
# 設定ファイルから読み込み(1 回だけ)
return {"debug": True, "max_connections": 100}
def get(self, key: str) -> any:
return self.config.get(key)
# どのモジュールからでも同じ設定にアクセス
config = ConfigManager()
print(config.get("debug")) # True
1.5 注意点
シングルトンの問題点:
- グローバル状態(テスト困難)
- 並列処理での初期化競合(スレッドセーフでない)
- 依存関係が隠蔽される
現代の代替案: 依存性注入(DI)コンテナを使用
2. Factory(ファクトリ)——オブジェクト生成をカプセル化
2.1 目的
オブジェクトの生成ロジックを 1 箇所に集約し、変更を局所化する。
2.2 実装(Python)
from abc import ABC, abstractmethod
# 製品インターフェース
class Payment(ABC):
@abstractmethod
def pay(self, amount: float) -> bool:
pass
# 具体製品
class CreditCardPayment(Payment):
def pay(self, amount: float) -> bool:
print(f"クレジットカードで{amount}円決済")
return True
class PayPalPayment(Payment):
def pay(self, amount: float) -> bool:
print(f"PayPal で{amount}円決済")
return True
class CryptoPayment(Payment):
def pay(self, amount: float) -> bool:
print(f"仮想通貨で{amount}円決済")
return True
# ファクトリ
class PaymentFactory:
@staticmethod
def create_payment(method: str) -> Payment:
if method == "credit_card":
return CreditCardPayment()
elif method == "paypal":
return PayPalPayment()
elif method == "crypto":
return CryptoPayment()
else:
raise ValueError(f"Unknown payment method: {method}")
# 使用例
payment = PaymentFactory.create_payment("crypto")
payment.pay(10000) # 仮想通貨で 10000 円決済
2.3 利点
| 利点 | 説明 |
|---|---|
| 開閉原則 | 新しい決済方法を追加しても既存コードを変更不要 |
| 単一責任 | 生成ロジックとビジネスロジックを分離 |
| テスト容易 | モックオブジェクトを簡単注入 |
3. Strategy(ストラテジー)——アルゴリズムの交換可能性
3.1 目的
アルゴリズムをカプセル化し、実行時に交換可能にする。
3.2 実装(Python)
from abc import ABC, abstractmethod
# 戦略インターフェース
class DiscountStrategy(ABC):
@abstractmethod
def calculate(self, price: float) -> float:
pass
# 具体戦略
class NoDiscount(DiscountStrategy):
def calculate(self, price: float) -> float:
return price
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: float):
self.percent = percent
def calculate(self, price: float) -> float:
return price * (1 - self.percent / 100)
class FixedDiscount(DiscountStrategy):
def __init__(self, amount: float):
self.amount = amount
def calculate(self, price: float) -> float:
return max(0, price - self.amount)
# コンテキスト
class ShoppingCart:
def __init__(self, discount_strategy: DiscountStrategy = None):
self.items = []
self.discount_strategy = discount_strategy or NoDiscount()
def set_strategy(self, strategy: DiscountStrategy):
self.discount_strategy = strategy
def add_item(self, price: float):
self.items.append(price)
def total(self) -> float:
subtotal = sum(self.items)
return self.discount_strategy.calculate(subtotal)
# 使用例
cart = ShoppingCart()
cart.add_item(10000)
cart.add_item(5000)
print(f"割引なし:{cart.total()}円") # 15000 円
# 戦略を运行时に変更
cart.set_strategy(PercentageDiscount(20))
print(f"20% 割引:{cart.total()}円") # 12000 円
cart.set_strategy(FixedDiscount(3000))
print(f"3000 円引き:{cart.total()}円") # 12000 円
3.3 実装(TypeScript)——戦略パターンと依存性注入
// 戦略インターフェース
interface SortStrategy<T> {
sort(items: T[]): T[];
}
// 具体戦略
class QuickSortStrategy<T> implements SortStrategy<T> {
sort(items: T[]): T[] {
console.log("クイックソート実行");
return [...items].sort(); // 簡略化
}
}
class MergeSortStrategy<T> implements SortStrategy<T> {
sort(items: T[]): T[] {
console.log("マージソート実行");
return [...items].sort(); // 簡略化
}
}
// コンテキスト
class Sorter<T> {
constructor(private strategy: SortStrategy<T>) {}
setStrategy(strategy: SortStrategy<T>): void {
this.strategy = strategy;
}
sort(items: T[]): T[] {
return this.strategy.sort(items);
}
}
// 使用例
const sorter = new Sorter<number>(new QuickSortStrategy());
sorter.sort([3, 1, 4, 1, 5]); // クイックソート実行
sorter.setStrategy(new MergeSortStrategy());
sorter.sort([3, 1, 4, 1, 5]); // マージソート実行
3.4 使用例:通知手段の切り替え
class NotificationStrategy(ABC):
@abstractmethod
def send(self, message: str, recipient: str):
pass
class EmailNotification(NotificationStrategy):
def send(self, message: str, recipient: str):
print(f"[Email] {recipient} へ:{message}")
class SMSNotification(NotificationStrategy):
def send(self, message: str, recipient: str):
print(f"[SMS] {recipient} へ:{message}")
class SlackNotification(NotificationStrategy):
def send(self, message: str, recipient: str):
print(f"[Slack] {recipient} へ:{message}")
class AlertService:
def __init__(self, strategy: NotificationStrategy = None):
self.strategy = strategy or EmailNotification()
def set_strategy(self, strategy: NotificationStrategy):
self.strategy = strategy
def alert(self, message: str, recipient: str):
self.strategy.send(message, recipient)
# 使用例
service = AlertService()
service.alert("サーバー障害発生", "admin@example.com")
# [Email] admin@example.com へ:サーバー障害発生
# 緊急時は SMS に切り替え
service.set_strategy(SMSNotification())
service.alert("緊急:サーバーダウン", "090-1234-5678")
# [SMS] 090-1234-5678 へ:緊急:サーバーダウン
4. Observer(オブザーバー)——状態変化の通知
4.1 目的
オブジェクトの状態変化を他のオブザーバーに自動通知する。
4.2 実装(Python)
from abc import ABC, abstractmethod
from typing import List
# オブザーバーインターフェース
class Observer(ABC):
@abstractmethod
def update(self, subject: 'Subject'):
pass
# 被観察者(Subject)
class StockPrice(Subject):
def __init__(self):
self._price: float = 0
self._observers: List[Observer] = []
def attach(self, observer: Observer):
self._observers.append(observer)
def detach(self, observer: Observer):
self._observers.remove(observer)
def notify(self):
for observer in self._observers:
observer.update(self)
def set_price(self, price: float):
self._price = price
print(f"株価更新:{price}円")
self.notify()
@property
def price(self) -> float:
return self._price
# 具体オブザーバー
class EmailAlert(Observer):
def update(self, subject: StockPrice):
if subject.price > 1000:
print(f"[メール通知] 株価が 1000 円を超えました:{subject.price}円")
class DisplayPanel(Observer):
def update(self, subject: StockPrice):
print(f"[ディスプレイ表示] 現在株価:{subject.price}円")
# 使用例
stock = StockPrice()
stock.attach(EmailAlert())
stock.attach(DisplayPanel())
stock.set_price(980) # ディスプレイのみ反応
stock.set_price(1020) # メール+ディスプレイ
4.3 実装(TypeScript)——イベントEmitter パターン
type Listener<T> = (data: T) => void;
class EventEmitter<T> {
private listeners: Set<Listener<T>> = new Set();
subscribe(listener: Listener<T>): () => void {
this.listeners.add(listener);
// unsubscribe 関数を返す
return () => this.listeners.delete(listener);
}
emit(data: T): void {
this.listeners.forEach(listener => listener(data));
}
}
// 使用例
const stockEmitter = new EventEmitter<number>();
const unsubscribe1 = stockEmitter.subscribe((price) => {
console.log(`[Alert] 株価:${price}円`);
});
const unsubscribe2 = stockEmitter.subscribe((price) => {
if (price > 1000) {
console.log(`[メール] 1000 円超:${price}円`);
}
});
stockEmitter.emit(980);
stockEmitter.emit(1020);
// 購読解除
unsubscribe1();
stockEmitter.emit(1100); // 2 番目のリスナーのみ反応
4.4 使用例:MVVM パターン
Observer パターンは MVVM(Model-View-ViewModel)の基盤となる。
class Model:
def __init__(self):
self._value = ""
self._observers = []
def attach(self, observer):
self._observers.append(observer)
def set_value(self, value: str):
self._value = value
self._notify()
def _notify(self):
for obs in self._observers:
obs(self._value)
class View:
def __init__(self, model: Model):
model.attach(self.render)
def render(self, value: str):
print(f"[View 描画] {value}")
# 使用例
model = Model()
view = View(model)
model.set_value("Hello") # [View 描画] Hello
model.set_value("World") # [View 描画] World
5. Adapter(アダプター)——インターフェースの互換性
5.1 目的
既存クラスのインターフェースを別のインターフェースに変換する。
5.2 実装
# 既存クラス(修正できないライブラリ)
class LegacyPayment:
def process_payment(self, amount: int, currency: str) -> bool:
print(f"[Legacy] {amount}{currency} を処理")
return True
# 新しいインターフェース
class ModernPayment(ABC):
@abstractmethod
def pay(self, amount: float, currency: str = "JPY") -> bool:
pass
# アダプター
class LegacyPaymentAdapter(ModernPayment):
def __init__(self, legacy: LegacyPayment):
self.legacy = legacy
def pay(self, amount: float, currency: str = "JPY") -> bool:
# 型変換(float → int)
return self.legacy.process_payment(int(amount), currency)
# 使用例
legacy = LegacyPayment()
adapter = LegacyPaymentAdapter(legacy)
adapter.pay(10000.50, "JPY") # [Legacy] 10000JPY を処理
5.3 使用例:外部 API ラッパー
# 外部天気 API(サードパーティ)
class ExternalWeatherAPI:
def get_weather_data(self, lat: float, lon: float) -> dict:
# 外部 API から生データを取得
return {"temp_c": 25.5, "humidity": 60, "condition": "sunny"}
# 自社のインターフェース
class WeatherService(ABC):
@abstractmethod
def get_weather(self, location: str) -> dict:
pass
# アダプター
class ExternalWeatherAdapter(WeatherService):
def __init__(self, api: ExternalWeatherAPI):
self.api = api
self._location_cache = {"tokyo": (35.68, 139.69)}
def get_weather(self, location: str) -> dict:
lat, lon = self._location_cache.get(location, (0, 0))
raw_data = self.api.get_weather_data(lat, lon)
# データ変換
return {
"location": location,
"temperature": raw_data["temp_c"],
"humidity_percent": raw_data["humidity"],
"condition": raw_data["condition"],
}
# 使用例
adapter = ExternalWeatherAdapter(ExternalWeatherAPI())
weather = adapter.get_weather("tokyo")
print(weather)
# {'location': 'tokyo', 'temperature': 25.5, 'humidity_percent': 60, 'condition': 'sunny'}
6. Decorator(デコレーター)——機能の動的追加
6.1 目的
オブジェクトに機能を動的に追加する。継承より柔軟。
6.2 実装(Python)
from abc import ABC, abstractmethod
# コンポーネント
class Coffee(ABC):
@abstractmethod
def cost(self) -> float:
pass
@abstractmethod
def description(self) -> str:
pass
# 具体コンポーネント
class SimpleCoffee(Coffee):
def cost(self) -> float:
return 300
def description(self) -> str:
return "シンプルコーヒー"
# デコレーター
class CoffeeDecorator(Coffee):
def __init__(self, coffee: Coffee):
self._coffee = coffee
def cost(self) -> float:
return self._coffee.cost()
def description(self) -> str:
return self._coffee.description()
# 具体デコレーター
class MilkDecorator(CoffeeDecorator):
def cost(self) -> float:
return self._coffee.cost() + 50
def description(self) -> str:
return self._coffee.description() + " +ミルク"
class SugarDecorator(CoffeeDecorator):
def cost(self) -> float:
return self._coffee.cost() + 20
def description(self) -> str:
return self._coffee.description() + " +砂糖"
# 使用例
coffee = SimpleCoffee()
print(f"{coffee.description()}: {coffee.cost()}円")
# シンプルコーヒー: 300 円
coffee = MilkDecorator(coffee)
print(f"{coffee.description()}: {coffee.cost()}円")
# シンプルコーヒー +ミルク:350 円
coffee = SugarDecorator(coffee)
print(f"{coffee.description()}: {coffee.cost()}円")
# シンプルコーヒー +ミルク + 砂糖:370 円
6.3 Python デコレーター構文
# 関数デコレーター
def log_execution(func):
def wrapper(*args, **kwargs):
print(f"[LOG] {func.__name__} 実行")
return func(*args, **kwargs)
return wrapper
@log_execution
def calculate_total(price, tax):
return price * (1 + tax)
print(calculate_total(1000, 0.1))
# [LOG] calculate_total 実行
# 1100.0
7. パターンの分類まとめ
7.1 GoF パターンの分類
| 分類 | パターン | 目的 |
|---|---|---|
| 生成に関するパターン | Singleton, Factory, Builder, Prototype, Abstract Factory | オブジェクトの生成メカニズム |
| 構造に関するパターン | Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy | クラスやオブジェクトの構成 |
| 振る舞いに関するパターン | Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor | オブジェクト間の責任分担 |
7.2 使用頻度の高いパターン TOP5
- Strategy - アルゴリズムの交換
- Observer - イベント通知
- Factory - オブジェクト生成
- Decorator - 機能追加
- Adapter - 互換性確保
7.3 パターン使用の注意点
アンチパターン:
- ゴールデンハンマー: すべての問題に同じパターンを適用
- オーバーエンジニアリング: 必要のない複雑化
- パターン万歳: パターンを使えばいい設計になるわけではない
適切な使用:
- まずシンプルに実装する
- 変更が必要になったらパターンを適用
- パターンは「手段」であって「目的」ではない
まとめ
デザインパターンの本質:
- 共通語彙として機能し、設計のコミュニケーションを円滑にする
- 変更に対する柔軟性を提供し、保守性を向上させる
- 再利用可能な知識として、車輪の再発明を防ぐ
しかし、パターンは適切な場面で使うべきだ。「まずは動くものを作り、リファクタリングでパターンを適用する」のが現代的なアプローチだ。
免責事項 — 掲載情報は執筆時点のものです。料金・機能は変更される場合があります。最新情報は各公式サイトをご確認ください。