Design Patterns
デザインパターン完全ガイド — ソフトウェア設計の定石を体系的に学ぶ
1. イントロダクション
1.1 デザインパターンとは何か
デザインパターン(Design Patterns)とは、ソフトウェア設計において繰り返し現れる問題に対する、再利用可能な解決策のテンプレートである。デザインパターンはそのままコードに変換できる完成品ではなく、さまざまな状況で適用できる問題解決のための「設計の定石」である。
建築の世界では、クリストファー・アレグザンダーが著書『パタン・ランゲージ(A Pattern Language)』で、建築設計における繰り返し現れる問題とその解決策を「パターン」として体系化した。この考え方をソフトウェア工学に応用したのが、ソフトウェアデザインパターンである。
1.2 GoF(Gang of Four)パターンの歴史
1994年、エーリッヒ・ガンマ(Erich Gamma)、リチャード・ヘルム(Richard Helm)、ラルフ・ジョンソン(Ralph Johnson)、ジョン・ブリシディース(John Vlissides)の4人が『Design Patterns: Elements of Reusable Object-Oriented Software(オブジェクト指向における再利用のためのデザインパターン)』を出版した。この4人は「Gang of Four(GoF)」と呼ばれ、本書で23のデザインパターンを体系的に分類・文書化した。
GoFパターンは以下の3カテゴリに分類される。
| カテゴリ | パターン数 | 目的 |
|---|---|---|
| 生成に関するパターン(Creational) | 5 | オブジェクトの生成メカニズムに関するパターン |
| 構造に関するパターン(Structural) | 7 | クラスやオブジェクトの合成に関するパターン |
| 振る舞いに関するパターン(Behavioral) | 11 | オブジェクト間の責任分担とアルゴリズムに関するパターン |
1.3 なぜデザインパターンが重要なのか
共通語彙の確立
デザインパターンを学ぶ最大のメリットの一つは、開発者間の共通語彙(ボキャブラリー)が確立されることである。「ここはStrategyパターンで実装しよう」と言えば、チームメンバー全員が同じ設計構造を思い浮かべることができる。これにより、設計に関するコミュニケーションコストが大幅に削減される。
再利用性と保守性の向上
デザインパターンは、長年の経験から洗練された設計手法であり、適切に適用することで以下のメリットが得られる。
- 再利用性: 一度理解すれば、異なるプロジェクト・異なる言語でも適用可能
- 保守性: SOLID原則に基づいた疎結合な設計を実現しやすい
- 拡張性: 変更に強い設計を構築できる(Open/Closed Principle)
- テスト容易性: 依存関係を適切に管理することで、ユニットテストが書きやすくなる
設計スキルの体系的向上
デザインパターンを学ぶことは、オブジェクト指向設計の原則(SOLID原則、DRY原則、KISS原則など)を実践的に理解する近道でもある。パターンの背後にある設計思想を理解することで、パターンに名前がついていない状況でも、適切な設計判断ができるようになる。
1.4 SOLID原則との関係
デザインパターンの多くは、SOLID原則を具現化したものである。
| 原則 | 略称 | 説明 | 関連パターン例 |
|---|---|---|---|
| 単一責任の原則 | SRP | クラスは変更の理由を一つだけ持つべき | Facade, Mediator |
| 開放閉鎖の原則 | OCP | 拡張に対して開き、修正に対して閉じるべき | Strategy, Decorator, Observer |
| リスコフの置換原則 | LSP | サブタイプはスーパータイプと置換可能であるべき | Template Method, Factory Method |
| インターフェース分離の原則 | ISP | クライアントに不要なインターフェースへの依存を強制しない | Adapter, Facade |
| 依存性逆転の原則 | DIP | 上位モジュールは下位モジュールに依存すべきではない | Abstract Factory, DI |
1.5 本記事の構成
本記事では、以下の構成でデザインパターンを網羅的に解説する。
- 生成に関するパターン — Singleton, Factory Method, Abstract Factory, Builder, Prototype
- 構造に関するパターン — Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy
- 振る舞いに関するパターン — Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor
- モダンなデザインパターン — DI, Repository, CQRS, Event Sourcing, Circuit Breaker, Saga, Sidecar
- アンチパターン — God Object, Spaghetti Code, Golden Hammer, Lava Flow
- 実践ガイド — パターンの選び方、組み合わせ方、リファクタリングでの活用
各パターンについて、Java、Python、TypeScriptでの具体的なコード例を示し、実際のフレームワークやライブラリでの使用例にも言及する。
1.6 対象読者
- オブジェクト指向プログラミングの基本を理解している開発者
- 設計スキルを体系的に向上させたいミドルレベルのエンジニア
- チーム開発で設計の共通語彙を持ちたいリーダー・アーキテクト
- クラウドネイティブ時代のモダンパターンも学びたい開発者
2. 生成に関するパターン(Creational Patterns)
生成に関するパターンは、オブジェクトの生成メカニズムを扱い、状況に適した方法でオブジェクトを作成する仕組みを提供する。直接的なインスタンス化の代わりにパターンを使うことで、柔軟性と再利用性が高まる。
2.1 Singleton(シングルトン)
目的
クラスのインスタンスが1つだけであることを保証し、そのインスタンスへのグローバルなアクセスポイントを提供する。
適用場面
- 設定管理(Configuration Manager)
- ログ管理(Logger)
- データベース接続プール
- キャッシュマネージャー
クラス図(テキスト表現)
┌──────────────────────────┐
│ Singleton │
├──────────────────────────┤
│ - instance: Singleton │
├──────────────────────────┤
│ - Singleton() │
│ + getInstance(): Singleton│
│ + operation(): void │
└──────────────────────────┘
Java実装
// スレッドセーフなSingleton(Double-Checked Locking)
public class DatabaseConnectionPool {
private static volatile DatabaseConnectionPool instance;
private final List<Connection> pool;
private DatabaseConnectionPool() {
pool = new ArrayList<>();
// 接続プールの初期化
for (int i = 0; i < 10; i++) {
pool.add(createConnection());
}
}
public static DatabaseConnectionPool getInstance() {
if (instance == null) {
synchronized (DatabaseConnectionPool.class) {
if (instance == null) {
instance = new DatabaseConnectionPool();
}
}
}
return instance;
}
public Connection getConnection() {
// プールから接続を取得
synchronized (pool) {
if (!pool.isEmpty()) {
return pool.remove(pool.size() - 1);
}
}
throw new RuntimeException("No available connections");
}
public void releaseConnection(Connection conn) {
synchronized (pool) {
pool.add(conn);
}
}
private Connection createConnection() {
// データベース接続を作成
return DriverManager.getConnection("jdbc:mysql://localhost/mydb");
}
}
// Enum方式(Joshua Bloch推奨 — 最も安全)
public enum AppConfig {
INSTANCE;
private final Properties properties = new Properties();
AppConfig() {
try (InputStream is = getClass().getResourceAsStream("/app.properties")) {
properties.load(is);
} catch (IOException e) {
throw new RuntimeException("Failed to load config", e);
}
}
public String get(String key) {
return properties.getProperty(key);
}
public String get(String key, String defaultValue) {
return properties.getProperty(key, defaultValue);
}
}
Python実装
import threading
from typing import Optional
class Logger:
"""スレッドセーフなSingletonロガー"""
_instance: Optional['Logger'] = None
_lock = threading.Lock()
def __new__(cls) -> 'Logger':
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if self._initialized:
return
self._initialized = True
self._log_file = open("app.log", "a")
self._write_lock = threading.Lock()
def info(self, message: str) -> None:
self._write("INFO", message)
def error(self, message: str) -> None:
self._write("ERROR", message)
def _write(self, level: str, message: str) -> None:
from datetime import datetime
timestamp = datetime.now().isoformat()
with self._write_lock:
self._log_file.write(f"[{timestamp}] [{level}] {message}\n")
self._log_file.flush()
# メタクラスを使った方式
class SingletonMeta(type):
_instances: dict = {}
_lock = threading.Lock()
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
with cls._lock:
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class CacheManager(metaclass=SingletonMeta):
def __init__(self):
self._cache: dict = {}
def get(self, key: str):
return self._cache.get(key)
def set(self, key: str, value) -> None:
self._cache[key] = value
TypeScript実装
class ConfigManager {
private static instance: ConfigManager;
private config: Map<string, string>;
private constructor() {
this.config = new Map();
this.loadConfig();
}
static getInstance(): ConfigManager {
if (!ConfigManager.instance) {
ConfigManager.instance = new ConfigManager();
}
return ConfigManager.instance;
}
private loadConfig(): void {
// 設定ファイルの読み込み
this.config.set("db.host", "localhost");
this.config.set("db.port", "5432");
this.config.set("app.env", "production");
}
get(key: string): string | undefined {
return this.config.get(key);
}
get(key: string, defaultValue: string): string {
return this.config.get(key) ?? defaultValue;
}
}
// 使用例
const config = ConfigManager.getInstance();
console.log(config.get("db.host")); // "localhost"
注意点
- Singletonはグローバルステートを導入するため、テストが難しくなる場合がある
- モダンな開発ではDI(依存性注入)コンテナでシングルトンスコープを使うことが推奨される
- マルチスレッド環境での実装に注意が必要
2.2 Factory Method(ファクトリーメソッド)
目的
オブジェクトの生成をサブクラスに委譲し、生成するオブジェクトのクラスをサブクラスが決定できるようにする。
適用場面
- ドキュメントの種類に応じたパーサーの生成
- 通知手段(メール、SMS、プッシュ通知)の切り替え
- データベースドライバの切り替え
クラス図(テキスト表現)
┌───────────────────┐ ┌──────────────────┐
│ Creator │ │ Product │
│ (abstract) │ │ (interface) │
├───────────────────┤ ├──────────────────┤
│+ factoryMethod() │────────>│+ operation() │
│+ someOperation() │ └──────────────────┘
└───────────────────┘ △
△ │
│ ┌────────┴────────┐
┌────────┴────────┐ ┌──────────┐ ┌──────────┐
│ ConcreteCreatorA │ │ProductA │ │ProductB │
├─────────────────┤ └──────────┘ └──────────┘
│+ factoryMethod() │
└─────────────────┘
Java実装
// Product インターフェース
public interface Notification {
void send(String recipient, String message);
String getType();
}
// Concrete Products
public class EmailNotification implements Notification {
@Override
public void send(String recipient, String message) {
System.out.println("Sending Email to " + recipient + ": " + message);
// SMTP送信ロジック
}
@Override
public String getType() { return "EMAIL"; }
}
public class SmsNotification implements Notification {
@Override
public void send(String recipient, String message) {
System.out.println("Sending SMS to " + recipient + ": " + message);
// SMS API呼び出しロジック
}
@Override
public String getType() { return "SMS"; }
}
public class PushNotification implements Notification {
@Override
public void send(String recipient, String message) {
System.out.println("Sending Push to " + recipient + ": " + message);
// プッシュ通知サービス呼び出し
}
@Override
public String getType() { return "PUSH"; }
}
// Creator(抽象クラス)
public abstract class NotificationFactory {
// Factory Method
public abstract Notification createNotification();
// テンプレートメソッド的な使い方
public void notifyUser(String recipient, String message) {
Notification notification = createNotification();
notification.send(recipient, message);
logNotification(notification.getType(), recipient);
}
private void logNotification(String type, String recipient) {
System.out.println("Logged: " + type + " sent to " + recipient);
}
}
// Concrete Creators
public class EmailNotificationFactory extends NotificationFactory {
@Override
public Notification createNotification() {
return new EmailNotification();
}
}
public class SmsNotificationFactory extends NotificationFactory {
@Override
public Notification createNotification() {
return new SmsNotification();
}
}
// 使用例
public class NotificationService {
public static void main(String[] args) {
NotificationFactory factory = new EmailNotificationFactory();
factory.notifyUser("user@example.com", "Welcome!");
factory = new SmsNotificationFactory();
factory.notifyUser("+81-90-1234-5678", "認証コード: 123456");
}
}
Python実装
from abc import ABC, abstractmethod
# Product
class Document(ABC):
@abstractmethod
def render(self) -> str:
pass
@abstractmethod
def get_content_type(self) -> str:
pass
class PdfDocument(Document):
def __init__(self, content: str):
self.content = content
def render(self) -> str:
return f"[PDF] {self.content}"
def get_content_type(self) -> str:
return "application/pdf"
class HtmlDocument(Document):
def __init__(self, content: str):
self.content = content
def render(self) -> str:
return f"<html><body>{self.content}</body></html>"
def get_content_type(self) -> str:
return "text/html"
class MarkdownDocument(Document):
def __init__(self, content: str):
self.content = content
def render(self) -> str:
return f"# Document\n\n{self.content}"
def get_content_type(self) -> str:
return "text/markdown"
# Creator
class DocumentFactory(ABC):
@abstractmethod
def create_document(self, content: str) -> Document:
pass
def generate_report(self, data: dict) -> Document:
"""テンプレートメソッドとしてのFactory Method使用"""
content = self._format_data(data)
doc = self.create_document(content)
return doc
def _format_data(self, data: dict) -> str:
return "\n".join(f"{k}: {v}" for k, v in data.items())
class PdfDocumentFactory(DocumentFactory):
def create_document(self, content: str) -> Document:
return PdfDocument(content)
class HtmlDocumentFactory(DocumentFactory):
def create_document(self, content: str) -> Document:
return HtmlDocument(content)
# 使用例
factory = HtmlDocumentFactory()
report = factory.generate_report({"title": "月次レポート", "date": "2026-04"})
print(report.render())
TypeScript実装
// Product
interface Transport {
deliver(cargo: string): void;
estimateCost(distance: number): number;
}
class Truck implements Transport {
deliver(cargo: string): void {
console.log(`Delivering "${cargo}" by truck on road`);
}
estimateCost(distance: number): number {
return distance * 1.5;
}
}
class Ship implements Transport {
deliver(cargo: string): void {
console.log(`Delivering "${cargo}" by ship on sea`);
}
estimateCost(distance: number): number {
return distance * 0.8;
}
}
// Creator
abstract class LogisticsFactory {
abstract createTransport(): Transport;
planDelivery(cargo: string, distance: number): void {
const transport = this.createTransport();
const cost = transport.estimateCost(distance);
console.log(`Estimated cost: $${cost}`);
transport.deliver(cargo);
}
}
class RoadLogistics extends LogisticsFactory {
createTransport(): Transport {
return new Truck();
}
}
class SeaLogistics extends LogisticsFactory {
createTransport(): Transport {
return new Ship();
}
}
// 使用例
const logistics: LogisticsFactory = new SeaLogistics();
logistics.planDelivery("Electronics", 500);
2.3 Abstract Factory(抽象ファクトリー)
目的
関連するオブジェクトのファミリーを、具体的なクラスを指定することなく生成するインターフェースを提供する。
適用場面
- クロスプラットフォームUIコンポーネント
- データベースアクセス層の抽象化
- テーマやスキンの切り替え
Java実装
// Abstract Products
public interface Button {
void render();
void onClick(Runnable action);
}
public interface TextField {
void render();
String getValue();
void setValue(String value);
}
public interface Checkbox {
void render();
boolean isChecked();
}
// Concrete Products — Material Design
public class MaterialButton implements Button {
@Override
public void render() {
System.out.println("[Material] Rendering elevated button with ripple effect");
}
@Override
public void onClick(Runnable action) {
System.out.println("[Material] Button clicked with animation");
action.run();
}
}
public class MaterialTextField implements TextField {
private String value = "";
@Override
public void render() {
System.out.println("[Material] Rendering outlined text field");
}
@Override
public String getValue() { return value; }
@Override
public void setValue(String value) { this.value = value; }
}
public class MaterialCheckbox implements Checkbox {
private boolean checked = false;
@Override
public void render() {
System.out.println("[Material] Rendering checkbox with animation");
}
@Override
public boolean isChecked() { return checked; }
}
// Concrete Products — iOS Style
public class CupertinoButton implements Button {
@Override
public void render() {
System.out.println("[Cupertino] Rendering iOS-style button");
}
@Override
public void onClick(Runnable action) {
System.out.println("[Cupertino] Button pressed with haptic feedback");
action.run();
}
}
public class CupertinoTextField implements TextField {
private String value = "";
@Override
public void render() {
System.out.println("[Cupertino] Rendering iOS-style text field");
}
@Override
public String getValue() { return value; }
@Override
public void setValue(String value) { this.value = value; }
}
public class CupertinoCheckbox implements Checkbox {
private boolean checked = false;
@Override
public void render() {
System.out.println("[Cupertino] Rendering iOS-style toggle switch");
}
@Override
public boolean isChecked() { return checked; }
}
// Abstract Factory
public interface UIComponentFactory {
Button createButton();
TextField createTextField();
Checkbox createCheckbox();
}
// Concrete Factories
public class MaterialUIFactory implements UIComponentFactory {
@Override
public Button createButton() { return new MaterialButton(); }
@Override
public TextField createTextField() { return new MaterialTextField(); }
@Override
public Checkbox createCheckbox() { return new MaterialCheckbox(); }
}
public class CupertinoUIFactory implements UIComponentFactory {
@Override
public Button createButton() { return new CupertinoButton(); }
@Override
public TextField createTextField() { return new CupertinoTextField(); }
@Override
public Checkbox createCheckbox() { return new CupertinoCheckbox(); }
}
// クライアントコード
public class LoginForm {
private final Button submitButton;
private final TextField usernameField;
private final TextField passwordField;
private final Checkbox rememberMe;
public LoginForm(UIComponentFactory factory) {
this.submitButton = factory.createButton();
this.usernameField = factory.createTextField();
this.passwordField = factory.createTextField();
this.rememberMe = factory.createCheckbox();
}
public void render() {
usernameField.render();
passwordField.render();
rememberMe.render();
submitButton.render();
}
}
Python実装
from abc import ABC, abstractmethod
# Abstract Products
class DatabaseConnection(ABC):
@abstractmethod
def connect(self, host: str, port: int) -> None: pass
@abstractmethod
def execute(self, query: str) -> list: pass
@abstractmethod
def close(self) -> None: pass
class DatabaseMigrator(ABC):
@abstractmethod
def migrate(self, version: str) -> None: pass
class QueryBuilder(ABC):
@abstractmethod
def select(self, table: str, columns: list[str]) -> str: pass
@abstractmethod
def insert(self, table: str, data: dict) -> str: pass
# Concrete Products — PostgreSQL
class PostgresConnection(DatabaseConnection):
def connect(self, host: str, port: int) -> None:
print(f"Connecting to PostgreSQL at {host}:{port}")
def execute(self, query: str) -> list:
print(f"[PostgreSQL] Executing: {query}")
return []
def close(self) -> None:
print("Closing PostgreSQL connection")
class PostgresMigrator(DatabaseMigrator):
def migrate(self, version: str) -> None:
print(f"[PostgreSQL] Running migration to version {version}")
class PostgresQueryBuilder(QueryBuilder):
def select(self, table: str, columns: list[str]) -> str:
cols = ", ".join(columns)
return f'SELECT {cols} FROM "{table}"'
def insert(self, table: str, data: dict) -> str:
cols = ", ".join(f'"{k}"' for k in data.keys())
vals = ", ".join(f"'{v}'" for v in data.values())
return f'INSERT INTO "{table}" ({cols}) VALUES ({vals})'
# Concrete Products — MySQL
class MySQLConnection(DatabaseConnection):
def connect(self, host: str, port: int) -> None:
print(f"Connecting to MySQL at {host}:{port}")
def execute(self, query: str) -> list:
print(f"[MySQL] Executing: {query}")
return []
def close(self) -> None:
print("Closing MySQL connection")
class MySQLMigrator(DatabaseMigrator):
def migrate(self, version: str) -> None:
print(f"[MySQL] Running migration to version {version}")
class MySQLQueryBuilder(QueryBuilder):
def select(self, table: str, columns: list[str]) -> str:
cols = ", ".join(columns)
return f"SELECT {cols} FROM `{table}`"
def insert(self, table: str, data: dict) -> str:
cols = ", ".join(f"`{k}`" for k in data.keys())
vals = ", ".join(f"'{v}'" for v in data.values())
return f"INSERT INTO `{table}` ({cols}) VALUES ({vals})"
# Abstract Factory
class DatabaseFactory(ABC):
@abstractmethod
def create_connection(self) -> DatabaseConnection: pass
@abstractmethod
def create_migrator(self) -> DatabaseMigrator: pass
@abstractmethod
def create_query_builder(self) -> QueryBuilder: pass
class PostgresFactory(DatabaseFactory):
def create_connection(self) -> DatabaseConnection:
return PostgresConnection()
def create_migrator(self) -> DatabaseMigrator:
return PostgresMigrator()
def create_query_builder(self) -> QueryBuilder:
return PostgresQueryBuilder()
class MySQLFactory(DatabaseFactory):
def create_connection(self) -> DatabaseConnection:
return MySQLConnection()
def create_migrator(self) -> DatabaseMigrator:
return MySQLMigrator()
def create_query_builder(self) -> QueryBuilder:
return MySQLQueryBuilder()
# 使用例
def setup_database(factory: DatabaseFactory):
conn = factory.create_connection()
conn.connect("localhost", 5432)
migrator = factory.create_migrator()
migrator.migrate("v2.0")
qb = factory.create_query_builder()
query = qb.select("users", ["id", "name", "email"])
conn.execute(query)
conn.close()
# PostgreSQLを使う場合
setup_database(PostgresFactory())
# MySQLに切り替える場合
setup_database(MySQLFactory())
2.4 Builder(ビルダー)
目的
複雑なオブジェクトの構築をその表現から分離し、同じ構築プロセスで異なる表現を作成できるようにする。
適用場面
- 多数のパラメータを持つオブジェクトの生成
- HTTPリクエストの構築
- SQLクエリの組み立て
- ドキュメントの生成
Java実装
// Product
public class HttpRequest {
private final String method;
private final String url;
private final Map<String, String> headers;
private final Map<String, String> queryParams;
private final String body;
private final int timeout;
private final boolean followRedirects;
private HttpRequest(Builder builder) {
this.method = builder.method;
this.url = builder.url;
this.headers = Collections.unmodifiableMap(builder.headers);
this.queryParams = Collections.unmodifiableMap(builder.queryParams);
this.body = builder.body;
this.timeout = builder.timeout;
this.followRedirects = builder.followRedirects;
}
// Getterメソッド省略
@Override
public String toString() {
return String.format("%s %s\nHeaders: %s\nBody: %s",
method, url, headers, body);
}
// Builder
public static class Builder {
private String method = "GET";
private String url;
private Map<String, String> headers = new HashMap<>();
private Map<String, String> queryParams = new HashMap<>();
private String body;
private int timeout = 30_000;
private boolean followRedirects = true;
public Builder(String url) {
this.url = Objects.requireNonNull(url);
}
public Builder method(String method) {
this.method = method;
return this;
}
public Builder header(String key, String value) {
this.headers.put(key, value);
return this;
}
public Builder queryParam(String key, String value) {
this.queryParams.put(key, value);
return this;
}
public Builder body(String body) {
this.body = body;
return this;
}
public Builder timeout(int millis) {
this.timeout = millis;
return this;
}
public Builder followRedirects(boolean follow) {
this.followRedirects = follow;
return this;
}
public HttpRequest build() {
// バリデーション
if (body != null && ("GET".equals(method) || "HEAD".equals(method))) {
throw new IllegalStateException(
method + " requests cannot have a body");
}
return new HttpRequest(this);
}
}
}
// 使用例
HttpRequest request = new HttpRequest.Builder("https://api.example.com/users")
.method("POST")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer token123")
.body("{\"name\": \"Taro\", \"email\": \"taro@example.com\"}")
.timeout(5_000)
.build();
Python実装
from dataclasses import dataclass, field
from typing import Optional
@dataclass(frozen=True)
class SqlQuery:
"""不変なSQLクエリオブジェクト"""
table: str
columns: list[str]
conditions: list[str]
order_by: Optional[str]
limit: Optional[int]
offset: Optional[int]
joins: list[str]
def to_sql(self) -> str:
parts = [f"SELECT {', '.join(self.columns)} FROM {self.table}"]
for join in self.joins:
parts.append(join)
if self.conditions:
parts.append(f"WHERE {' AND '.join(self.conditions)}")
if self.order_by:
parts.append(f"ORDER BY {self.order_by}")
if self.limit is not None:
parts.append(f"LIMIT {self.limit}")
if self.offset is not None:
parts.append(f"OFFSET {self.offset}")
return " ".join(parts)
class SqlQueryBuilder:
"""SQLクエリのビルダー"""
def __init__(self, table: str):
self._table = table
self._columns: list[str] = ["*"]
self._conditions: list[str] = []
self._order_by: Optional[str] = None
self._limit: Optional[int] = None
self._offset: Optional[int] = None
self._joins: list[str] = []
def select(self, *columns: str) -> "SqlQueryBuilder":
self._columns = list(columns)
return self
def where(self, condition: str) -> "SqlQueryBuilder":
self._conditions.append(condition)
return self
def join(self, table: str, on: str, join_type: str = "INNER") -> "SqlQueryBuilder":
self._joins.append(f"{join_type} JOIN {table} ON {on}")
return self
def order_by(self, column: str, direction: str = "ASC") -> "SqlQueryBuilder":
self._order_by = f"{column} {direction}"
return self
def limit(self, count: int) -> "SqlQueryBuilder":
self._limit = count
return self
def offset(self, count: int) -> "SqlQueryBuilder":
self._offset = count
return self
def build(self) -> SqlQuery:
return SqlQuery(
table=self._table,
columns=self._columns,
conditions=self._conditions,
order_by=self._order_by,
limit=self._limit,
offset=self._offset,
joins=self._joins,
)
# 使用例
query = (
SqlQueryBuilder("users")
.select("u.id", "u.name", "u.email", "o.total")
.join("orders o", "u.id = o.user_id", "LEFT")
.where("u.active = true")
.where("o.created_at > '2026-01-01'")
.order_by("o.total", "DESC")
.limit(20)
.offset(0)
.build()
)
print(query.to_sql())
# SELECT u.id, u.name, u.email, o.total FROM users
# LEFT JOIN orders o ON u.id = o.user_id
# WHERE u.active = true AND o.created_at > '2026-01-01'
# ORDER BY o.total DESC LIMIT 20 OFFSET 0
TypeScript実装
interface EmailMessage {
readonly from: string;
readonly to: string[];
readonly cc: string[];
readonly bcc: string[];
readonly subject: string;
readonly textBody?: string;
readonly htmlBody?: string;
readonly attachments: Array<{ filename: string; content: Buffer }>;
readonly priority: "high" | "normal" | "low";
}
class EmailBuilder {
private _from: string = "";
private _to: string[] = [];
private _cc: string[] = [];
private _bcc: string[] = [];
private _subject: string = "";
private _textBody?: string;
private _htmlBody?: string;
private _attachments: Array<{ filename: string; content: Buffer }> = [];
private _priority: "high" | "normal" | "low" = "normal";
from(address: string): this {
this._from = address;
return this;
}
to(...addresses: string[]): this {
this._to.push(...addresses);
return this;
}
cc(...addresses: string[]): this {
this._cc.push(...addresses);
return this;
}
bcc(...addresses: string[]): this {
this._bcc.push(...addresses);
return this;
}
subject(subject: string): this {
this._subject = subject;
return this;
}
textBody(body: string): this {
this._textBody = body;
return this;
}
htmlBody(body: string): this {
this._htmlBody = body;
return this;
}
attach(filename: string, content: Buffer): this {
this._attachments.push({ filename, content });
return this;
}
priority(p: "high" | "normal" | "low"): this {
this._priority = p;
return this;
}
build(): EmailMessage {
if (!this._from) throw new Error("From address is required");
if (this._to.length === 0) throw new Error("At least one recipient is required");
if (!this._subject) throw new Error("Subject is required");
return {
from: this._from,
to: [...this._to],
cc: [...this._cc],
bcc: [...this._bcc],
subject: this._subject,
textBody: this._textBody,
htmlBody: this._htmlBody,
attachments: [...this._attachments],
priority: this._priority,
};
}
}
// 使用例
const email = new EmailBuilder()
.from("system@example.com")
.to("user@example.com", "admin@example.com")
.cc("manager@example.com")
.subject("月次レポート")
.htmlBody("<h1>Monthly Report</h1><p>詳細は添付をご確認ください。</p>")
.priority("high")
.build();
2.5 Prototype(プロトタイプ)
目的
既存のオブジェクトをコピー(クローン)して新しいオブジェクトを生成する。生成コストが高いオブジェクトの複製に有効。
適用場面
- ゲームオブジェクトの複製
- 設定テンプレートからのインスタンス生成
- 高コストな初期化を避けるための複製
Java実装
// Prototypeインターフェース
public interface DocumentPrototype extends Cloneable {
DocumentPrototype clone();
void customize(String title, String author);
void print();
}
// ConcretePrototype
public class ReportTemplate implements DocumentPrototype {
private String title;
private String author;
private String header;
private String footer;
private List<String> sections;
private Map<String, String> styles;
public ReportTemplate() {
// コストの高い初期化
this.sections = new ArrayList<>();
this.styles = new HashMap<>();
loadDefaultStyles();
}
private ReportTemplate(ReportTemplate source) {
this.title = source.title;
this.author = source.author;
this.header = source.header;
this.footer = source.footer;
this.sections = new ArrayList<>(source.sections);
this.styles = new HashMap<>(source.styles);
}
private void loadDefaultStyles() {
// 重い初期化処理(ファイル読み込みなど)
styles.put("font", "Noto Sans JP");
styles.put("fontSize", "12pt");
styles.put("margin", "2cm");
styles.put("lineHeight", "1.6");
}
@Override
public DocumentPrototype clone() {
return new ReportTemplate(this);
}
@Override
public void customize(String title, String author) {
this.title = title;
this.author = author;
}
public void addSection(String section) {
this.sections.add(section);
}
@Override
public void print() {
System.out.printf("Report: %s by %s%n", title, author);
System.out.printf("Styles: %s%n", styles);
sections.forEach(s -> System.out.println(" - " + s));
}
}
// Prototypeレジストリ
public class DocumentRegistry {
private final Map<String, DocumentPrototype> prototypes = new HashMap<>();
public void register(String key, DocumentPrototype prototype) {
prototypes.put(key, prototype);
}
public DocumentPrototype create(String key) {
DocumentPrototype prototype = prototypes.get(key);
if (prototype == null) {
throw new IllegalArgumentException("Unknown template: " + key);
}
return prototype.clone();
}
}
// 使用例
DocumentRegistry registry = new DocumentRegistry();
ReportTemplate monthlyTemplate = new ReportTemplate();
monthlyTemplate.customize("月次レポートテンプレート", "System");
monthlyTemplate.addSection("売上概要");
monthlyTemplate.addSection("KPI分析");
registry.register("monthly-report", monthlyTemplate);
// テンプレートから新しいドキュメントを生成(高コストな初期化をスキップ)
DocumentPrototype aprilReport = registry.create("monthly-report");
aprilReport.customize("2026年4月 月次レポート", "田中太郎");
aprilReport.print();
Python実装
import copy
from typing import Any
class GameCharacter:
"""ゲームキャラクターのPrototype"""
def __init__(self, name: str, char_class: str):
self.name = name
self.char_class = char_class
self.level = 1
self.stats: dict[str, int] = {}
self.skills: list[str] = []
self.equipment: dict[str, str] = {}
self._load_base_stats() # 重い初期化
def _load_base_stats(self):
"""基本ステータスの読み込み(コストが高い処理を想定)"""
base_stats = {
"warrior": {"hp": 100, "mp": 30, "str": 15, "def": 12, "spd": 8},
"mage": {"hp": 60, "mp": 100, "str": 5, "def": 6, "spd": 10},
"rogue": {"hp": 75, "mp": 50, "str": 10, "def": 8, "spd": 15},
}
self.stats = base_stats.get(self.char_class, {})
def clone(self) -> "GameCharacter":
"""ディープコピーによるクローン"""
return copy.deepcopy(self)
def set_name(self, name: str) -> "GameCharacter":
self.name = name
return self
def add_skill(self, skill: str) -> "GameCharacter":
self.skills.append(skill)
return self
def equip(self, slot: str, item: str) -> "GameCharacter":
self.equipment[slot] = item
return self
def __str__(self) -> str:
return (
f"{self.name} (Lv.{self.level} {self.char_class})\n"
f" Stats: {self.stats}\n"
f" Skills: {self.skills}\n"
f" Equipment: {self.equipment}"
)
# プロトタイプレジストリ
class CharacterFactory:
_prototypes: dict[str, GameCharacter] = {}
@classmethod
def register(cls, key: str, prototype: GameCharacter) -> None:
cls._prototypes[key] = prototype
@classmethod
def create(cls, key: str) -> GameCharacter:
proto = cls._prototypes.get(key)
if proto is None:
raise KeyError(f"Unknown prototype: {key}")
return proto.clone()
# テンプレートの登録
warrior_template = GameCharacter("Warrior Template", "warrior")
warrior_template.add_skill("斬撃")
warrior_template.add_skill("防御態勢")
warrior_template.equip("weapon", "鉄の剣")
warrior_template.equip("armor", "鉄の鎧")
CharacterFactory.register("warrior", warrior_template)
# テンプレートからキャラクターを生成
player1 = CharacterFactory.create("warrior").set_name("勇者タロウ")
player2 = CharacterFactory.create("warrior").set_name("戦士ハナコ")
player2.add_skill("大回転斬り") # 個別カスタマイズ
print(player1)
print(player2)
TypeScript実装
interface Cloneable<T> {
clone(): T;
}
class ChartConfig implements Cloneable<ChartConfig> {
title: string = "";
type: "bar" | "line" | "pie" = "bar";
colors: string[] = [];
width: number = 800;
height: number = 400;
legend: boolean = true;
gridLines: boolean = true;
animation: { duration: number; easing: string } = {
duration: 300,
easing: "ease-in-out",
};
clone(): ChartConfig {
const cloned = new ChartConfig();
cloned.title = this.title;
cloned.type = this.type;
cloned.colors = [...this.colors];
cloned.width = this.width;
cloned.height = this.height;
cloned.legend = this.legend;
cloned.gridLines = this.gridLines;
cloned.animation = { ...this.animation };
return cloned;
}
}
// レジストリ
class ChartConfigRegistry {
private static templates = new Map<string, ChartConfig>();
static register(name: string, config: ChartConfig): void {
this.templates.set(name, config);
}
static create(name: string): ChartConfig {
const template = this.templates.get(name);
if (!template) throw new Error(`Unknown template: ${name}`);
return template.clone();
}
}
// テンプレート登録
const dashboardChart = new ChartConfig();
dashboardChart.colors = ["#3B82F6", "#EF4444", "#10B981", "#F59E0B"];
dashboardChart.width = 600;
dashboardChart.height = 300;
dashboardChart.animation = { duration: 500, easing: "ease-out" };
ChartConfigRegistry.register("dashboard", dashboardChart);
// テンプレートから生成
const salesChart = ChartConfigRegistry.create("dashboard");
salesChart.title = "月別売上";
salesChart.type = "bar";
const trafficChart = ChartConfigRegistry.create("dashboard");
trafficChart.title = "アクセス推移";
trafficChart.type = "line";
2.6 生成パターンのまとめ
| パターン | 目的 | 使いどころ |
|---|---|---|
| Singleton | インスタンスを1つに制限 | 設定管理、ロガー、接続プール |
| Factory Method | 生成をサブクラスに委譲 | 生成するクラスを実行時に決定したい場合 |
| Abstract Factory | 関連オブジェクト群の生成 | クロスプラットフォームUI、DB抽象化 |
| Builder | 複雑なオブジェクトの段階的構築 | 多数のパラメータ、不変オブジェクト |
| Prototype | 既存オブジェクトの複製 | 初期化コストが高いオブジェクト |
3. 構造に関するパターン(Structural Patterns)
構造に関するパターンは、クラスやオブジェクトを組み合わせて、より大きな構造を作る方法を扱う。既存のクラスやオブジェクトの構造を変更せずに、新しい機能を追加したり、互換性を確保したりするための手法を提供する。
3.1 Adapter(アダプター)
目的
互換性のないインターフェースを持つクラス同士を接続し、協調動作できるようにする。既存のクラスを変更せずに、異なるインターフェースに適合させる。
適用場面
- レガシーシステムとの統合
- サードパーティライブラリのインターフェース統一
- 異なるAPIフォーマットの統一
実際のフレームワークでの使用例
- Java:
InputStreamReader(InputStreamをReaderに変換) - Spring:
HandlerAdapter - Python:
json.JSONEncoderのサブクラス化
Java実装
// 既存のレガシー決済システム(変更不可)
public class LegacyPaymentProcessor {
public boolean processPayment(String cardNumber, double amount, String currency) {
System.out.printf("Legacy: Processing %s %.2f with card %s%n",
currency, amount, cardNumber);
return true;
}
public PaymentStatus checkStatus(String transactionId) {
return new PaymentStatus(transactionId, "COMPLETED");
}
}
// 新しいインターフェース
public interface ModernPaymentGateway {
PaymentResult charge(PaymentRequest request);
PaymentResult refund(String transactionId, Money amount);
TransactionStatus getStatus(String transactionId);
}
public record PaymentRequest(String paymentMethodToken, Money amount, String description) {}
public record Money(BigDecimal amount, String currency) {}
public record PaymentResult(boolean success, String transactionId, String message) {}
// Adapter
public class LegacyPaymentAdapter implements ModernPaymentGateway {
private final LegacyPaymentProcessor legacyProcessor;
private final TokenResolver tokenResolver;
public LegacyPaymentAdapter(LegacyPaymentProcessor legacyProcessor,
TokenResolver tokenResolver) {
this.legacyProcessor = legacyProcessor;
this.tokenResolver = tokenResolver;
}
@Override
public PaymentResult charge(PaymentRequest request) {
// トークンからカード番号を解決
String cardNumber = tokenResolver.resolve(request.paymentMethodToken());
// レガシーAPIの呼び出し
boolean success = legacyProcessor.processPayment(
cardNumber,
request.amount().amount().doubleValue(),
request.amount().currency()
);
String txnId = generateTransactionId();
return new PaymentResult(success, txnId,
success ? "Payment processed" : "Payment failed");
}
@Override
public PaymentResult refund(String transactionId, Money amount) {
// レガシーシステムには返金APIがないため、負の金額で処理
boolean success = legacyProcessor.processPayment(
"REFUND-" + transactionId,
-amount.amount().doubleValue(),
amount.currency()
);
return new PaymentResult(success, transactionId, "Refund processed");
}
@Override
public TransactionStatus getStatus(String transactionId) {
PaymentStatus status = legacyProcessor.checkStatus(transactionId);
return mapStatus(status);
}
private TransactionStatus mapStatus(PaymentStatus legacy) {
return switch (legacy.status()) {
case "COMPLETED" -> TransactionStatus.SUCCESS;
case "PENDING" -> TransactionStatus.PROCESSING;
case "FAILED" -> TransactionStatus.FAILED;
default -> TransactionStatus.UNKNOWN;
};
}
private String generateTransactionId() {
return "TXN-" + System.currentTimeMillis();
}
}
Python実装
from abc import ABC, abstractmethod
from dataclasses import dataclass
import json
import xml.etree.ElementTree as ET
# ターゲットインターフェース
class DataParser(ABC):
@abstractmethod
def parse(self, raw_data: str) -> list[dict]:
pass
@abstractmethod
def serialize(self, data: list[dict]) -> str:
pass
# 既存のXMLパーサー(サードパーティ — 変更不可)
class XmlDataProcessor:
def read_xml(self, xml_string: str) -> ET.Element:
return ET.fromstring(xml_string)
def extract_records(self, root: ET.Element) -> list[dict]:
records = []
for item in root.findall(".//record"):
record = {child.tag: child.text for child in item}
records.append(record)
return records
def build_xml(self, records: list[dict], root_tag: str = "data") -> str:
root = ET.Element(root_tag)
for record in records:
item = ET.SubElement(root, "record")
for key, value in record.items():
child = ET.SubElement(item, key)
child.text = str(value)
return ET.tostring(root, encoding="unicode")
# Adapter
class XmlParserAdapter(DataParser):
def __init__(self):
self._processor = XmlDataProcessor()
def parse(self, raw_data: str) -> list[dict]:
root = self._processor.read_xml(raw_data)
return self._processor.extract_records(root)
def serialize(self, data: list[dict]) -> str:
return self._processor.build_xml(data)
class JsonParser(DataParser):
def parse(self, raw_data: str) -> list[dict]:
return json.loads(raw_data)
def serialize(self, data: list[dict]) -> str:
return json.dumps(data, ensure_ascii=False, indent=2)
# クライアントコード
def process_data(parser: DataParser, raw_data: str) -> None:
records = parser.parse(raw_data)
for record in records:
print(f" Name: {record.get('name')}, Value: {record.get('value')}")
# XMLデータもJSONデータも同じインターフェースで処理可能
xml_data = """<data>
<record><name>Temperature</name><value>25.5</value></record>
<record><name>Humidity</name><value>60.2</value></record>
</data>"""
json_data = '[{"name": "Temperature", "value": "25.5"}]'
process_data(XmlParserAdapter(), xml_data)
process_data(JsonParser(), json_data)
TypeScript実装
// ターゲットインターフェース
interface Logger {
debug(message: string, context?: Record<string, unknown>): void;
info(message: string, context?: Record<string, unknown>): void;
warn(message: string, context?: Record<string, unknown>): void;
error(message: string, context?: Record<string, unknown>): void;
}
// サードパーティのロガー(変更不可)
class WinstonLikeLogger {
log(level: string, msg: string, meta?: object): void {
console.log(`[${level.toUpperCase()}] ${msg}`, meta || "");
}
}
// Adapter
class WinstonLoggerAdapter implements Logger {
constructor(private readonly winston: WinstonLikeLogger) {}
debug(message: string, context?: Record<string, unknown>): void {
this.winston.log("debug", message, context);
}
info(message: string, context?: Record<string, unknown>): void {
this.winston.log("info", message, context);
}
warn(message: string, context?: Record<string, unknown>): void {
this.winston.log("warn", message, context);
}
error(message: string, context?: Record<string, unknown>): void {
this.winston.log("error", message, context);
}
}
// 使用例
const logger: Logger = new WinstonLoggerAdapter(new WinstonLikeLogger());
logger.info("User logged in", { userId: "12345" });
3.2 Bridge(ブリッジ)
目的
抽象化と実装を分離し、それぞれを独立に変更できるようにする。「継承」ではなく「委譲」により柔軟な構造を実現する。
Java実装
// Implementation(実装の階層)
public interface MessageSender {
void send(String title, String content, String recipient);
}
public class EmailSender implements MessageSender {
@Override
public void send(String title, String content, String recipient) {
System.out.printf("Email to %s: [%s] %s%n", recipient, title, content);
}
}
public class SmsSender implements MessageSender {
@Override
public void send(String title, String content, String recipient) {
// SMSは件名をサポートしないので本文のみ
String smsContent = title + ": " + content;
if (smsContent.length() > 160) {
smsContent = smsContent.substring(0, 157) + "...";
}
System.out.printf("SMS to %s: %s%n", recipient, smsContent);
}
}
public class SlackSender implements MessageSender {
@Override
public void send(String title, String content, String recipient) {
System.out.printf("Slack to %s: *%s*\n%s%n", recipient, title, content);
}
}
// Abstraction(抽象の階層)
public abstract class NotificationMessage {
protected MessageSender sender;
public NotificationMessage(MessageSender sender) {
this.sender = sender;
}
public abstract void notify(String recipient);
}
// Refined Abstractions
public class AlertNotification extends NotificationMessage {
private final String alertLevel;
private final String message;
public AlertNotification(MessageSender sender, String alertLevel, String message) {
super(sender);
this.alertLevel = alertLevel;
this.message = message;
}
@Override
public void notify(String recipient) {
String title = String.format("[%s ALERT]", alertLevel.toUpperCase());
String content = String.format("ALERT: %s\nLevel: %s\nTime: %s",
message, alertLevel, java.time.Instant.now());
sender.send(title, content, recipient);
}
}
public class PromotionNotification extends NotificationMessage {
private final String promoName;
private final String description;
private final String expiryDate;
public PromotionNotification(MessageSender sender, String promoName,
String description, String expiryDate) {
super(sender);
this.promoName = promoName;
this.description = description;
this.expiryDate = expiryDate;
}
@Override
public void notify(String recipient) {
String title = "Special Offer: " + promoName;
String content = String.format("%s\n有効期限: %s", description, expiryDate);
sender.send(title, content, recipient);
}
}
// 使用例 — 抽象と実装を自由に組み合わせ可能
NotificationMessage alert = new AlertNotification(new SlackSender(), "critical", "CPU > 95%");
alert.notify("#ops-alerts");
NotificationMessage promo = new PromotionNotification(new EmailSender(),
"春のキャンペーン", "全商品20%OFF", "2026-04-30");
promo.notify("customer@example.com");
Python実装
from abc import ABC, abstractmethod
# Implementation
class Renderer(ABC):
@abstractmethod
def render_title(self, title: str) -> str: pass
@abstractmethod
def render_paragraph(self, text: str) -> str: pass
@abstractmethod
def render_list(self, items: list[str]) -> str: pass
class HtmlRenderer(Renderer):
def render_title(self, title: str) -> str:
return f"<h1>{title}</h1>"
def render_paragraph(self, text: str) -> str:
return f"<p>{text}</p>"
def render_list(self, items: list[str]) -> str:
li = "".join(f"<li>{item}</li>" for item in items)
return f"<ul>{li}</ul>"
class MarkdownRenderer(Renderer):
def render_title(self, title: str) -> str:
return f"# {title}\n"
def render_paragraph(self, text: str) -> str:
return f"{text}\n"
def render_list(self, items: list[str]) -> str:
return "\n".join(f"- {item}" for item in items) + "\n"
class PlainTextRenderer(Renderer):
def render_title(self, title: str) -> str:
return f"{title}\n{'=' * len(title)}\n"
def render_paragraph(self, text: str) -> str:
return f"{text}\n"
def render_list(self, items: list[str]) -> str:
return "\n".join(f" * {item}" for item in items) + "\n"
# Abstraction
class Report(ABC):
def __init__(self, renderer: Renderer):
self.renderer = renderer
@abstractmethod
def generate(self) -> str: pass
class SalesReport(Report):
def __init__(self, renderer: Renderer, data: dict):
super().__init__(renderer)
self.data = data
def generate(self) -> str:
parts = [
self.renderer.render_title("売上レポート"),
self.renderer.render_paragraph(f"期間: {self.data['period']}"),
self.renderer.render_paragraph(f"総売上: ¥{self.data['total']:,}"),
self.renderer.render_title("カテゴリ別"),
self.renderer.render_list([
f"{k}: ¥{v:,}" for k, v in self.data["categories"].items()
]),
]
return "\n".join(parts)
# Rendererを差し替えるだけで出力形式が変わる
data = {"period": "2026年4月", "total": 5_000_000,
"categories": {"Electronics": 2_000_000, "Books": 1_500_000, "Food": 1_500_000}}
html_report = SalesReport(HtmlRenderer(), data)
print(html_report.generate())
md_report = SalesReport(MarkdownRenderer(), data)
print(md_report.generate())
3.3 Composite(コンポジット)
目的
オブジェクトをツリー構造に組み合わせ、個々のオブジェクトとオブジェクトの集合を同一視(統一的に扱う)できるようにする。
実際の使用例
- ファイルシステム(ファイルとディレクトリ)
- UIコンポーネントのツリー(React, Swing)
- 組織図
Java実装
// Component
public interface FileSystemEntry {
String getName();
long getSize();
void display(String indent);
FileSystemEntry find(String name);
}
// Leaf
public class File implements FileSystemEntry {
private final String name;
private final long size;
public File(String name, long size) {
this.name = name;
this.size = size;
}
@Override public String getName() { return name; }
@Override public long getSize() { return size; }
@Override
public void display(String indent) {
System.out.printf("%s📄 %s (%d bytes)%n", indent, name, size);
}
@Override
public FileSystemEntry find(String name) {
return this.name.equals(name) ? this : null;
}
}
// Composite
public class Directory implements FileSystemEntry {
private final String name;
private final List<FileSystemEntry> entries = new ArrayList<>();
public Directory(String name) { this.name = name; }
public void add(FileSystemEntry entry) { entries.add(entry); }
public void remove(FileSystemEntry entry) { entries.remove(entry); }
@Override public String getName() { return name; }
@Override
public long getSize() {
return entries.stream().mapToLong(FileSystemEntry::getSize).sum();
}
@Override
public void display(String indent) {
System.out.printf("%s📁 %s/ (%d bytes)%n", indent, name, getSize());
entries.forEach(e -> e.display(indent + " "));
}
@Override
public FileSystemEntry find(String name) {
if (this.name.equals(name)) return this;
for (FileSystemEntry entry : entries) {
FileSystemEntry found = entry.find(name);
if (found != null) return found;
}
return null;
}
}
// 使用例
Directory root = new Directory("project");
Directory src = new Directory("src");
src.add(new File("Main.java", 2048));
src.add(new File("Utils.java", 1024));
Directory test = new Directory("test");
test.add(new File("MainTest.java", 1536));
root.add(src);
root.add(test);
root.add(new File("pom.xml", 512));
root.display("");
TypeScript実装
// Component
interface UIComponent {
render(depth: number): string;
getWidth(): number;
getHeight(): number;
}
// Leaf
class TextElement implements UIComponent {
constructor(
private text: string,
private fontSize: number = 14
) {}
render(depth: number): string {
const indent = " ".repeat(depth);
return `${indent}<Text size=${this.fontSize}>${this.text}</Text>`;
}
getWidth(): number { return this.text.length * this.fontSize * 0.6; }
getHeight(): number { return this.fontSize * 1.5; }
}
class ImageElement implements UIComponent {
constructor(
private src: string,
private width: number,
private height: number
) {}
render(depth: number): string {
const indent = " ".repeat(depth);
return `${indent}<Image src="${this.src}" ${this.width}x${this.height} />`;
}
getWidth(): number { return this.width; }
getHeight(): number { return this.height; }
}
// Composite
class ContainerElement implements UIComponent {
private children: UIComponent[] = [];
constructor(
private name: string,
private direction: "row" | "column" = "column"
) {}
add(child: UIComponent): this {
this.children.push(child);
return this;
}
render(depth: number): string {
const indent = " ".repeat(depth);
const childrenRendered = this.children
.map((c) => c.render(depth + 1))
.join("\n");
return `${indent}<${this.name} direction="${this.direction}">\n${childrenRendered}\n${indent}</${this.name}>`;
}
getWidth(): number {
if (this.direction === "row") {
return this.children.reduce((sum, c) => sum + c.getWidth(), 0);
}
return Math.max(...this.children.map((c) => c.getWidth()), 0);
}
getHeight(): number {
if (this.direction === "column") {
return this.children.reduce((sum, c) => sum + c.getHeight(), 0);
}
return Math.max(...this.children.map((c) => c.getHeight()), 0);
}
}
// 使用例
const page = new ContainerElement("Page")
.add(
new ContainerElement("Header", "row")
.add(new ImageElement("logo.png", 50, 50))
.add(new TextElement("My Application", 24))
)
.add(
new ContainerElement("Content")
.add(new TextElement("Welcome to the app"))
.add(new ImageElement("banner.jpg", 800, 200))
);
console.log(page.render(0));
console.log(`Total size: ${page.getWidth()} x ${page.getHeight()}`);
3.4 Decorator(デコレーター)
目的
オブジェクトに動的に新しい責務(機能)を追加する。サブクラス化に代わる柔軟な拡張手段を提供する。
実際のフレームワークでの使用例
- Java:
BufferedReader(new InputStreamReader(new FileInputStream(...))) - Python:
@staticmethod,@classmethod,@property(言語レベルのデコレーター) - TypeScript:
@Injectable(),@Component()(Angularデコレーター)
Java実装
// Component
public interface DataStream {
void write(byte[] data);
byte[] read();
String getDescription();
}
// ConcreteComponent
public class FileDataStream implements DataStream {
private final String filename;
private byte[] buffer = new byte[0];
public FileDataStream(String filename) {
this.filename = filename;
}
@Override
public void write(byte[] data) {
this.buffer = data;
System.out.println("Writing " + data.length + " bytes to " + filename);
}
@Override
public byte[] read() {
System.out.println("Reading from " + filename);
return buffer;
}
@Override
public String getDescription() {
return "FileStream(" + filename + ")";
}
}
// Base Decorator
public abstract class DataStreamDecorator implements DataStream {
protected final DataStream wrapped;
public DataStreamDecorator(DataStream wrapped) {
this.wrapped = wrapped;
}
}
// ConcreteDecorators
public class EncryptionDecorator extends DataStreamDecorator {
private final String algorithm;
public EncryptionDecorator(DataStream wrapped, String algorithm) {
super(wrapped);
this.algorithm = algorithm;
}
@Override
public void write(byte[] data) {
byte[] encrypted = encrypt(data);
wrapped.write(encrypted);
}
@Override
public byte[] read() {
byte[] data = wrapped.read();
return decrypt(data);
}
@Override
public String getDescription() {
return "Encrypted[" + algorithm + "](" + wrapped.getDescription() + ")";
}
private byte[] encrypt(byte[] data) {
System.out.println("Encrypting with " + algorithm);
return data; // 簡略化
}
private byte[] decrypt(byte[] data) {
System.out.println("Decrypting with " + algorithm);
return data;
}
}
public class CompressionDecorator extends DataStreamDecorator {
@Override
public void write(byte[] data) {
byte[] compressed = compress(data);
wrapped.write(compressed);
}
@Override
public byte[] read() {
byte[] data = wrapped.read();
return decompress(data);
}
@Override
public String getDescription() {
return "Compressed(" + wrapped.getDescription() + ")";
}
public CompressionDecorator(DataStream wrapped) { super(wrapped); }
private byte[] compress(byte[] data) {
System.out.println("Compressing data");
return data;
}
private byte[] decompress(byte[] data) {
System.out.println("Decompressing data");
return data;
}
}
public class LoggingDecorator extends DataStreamDecorator {
public LoggingDecorator(DataStream wrapped) { super(wrapped); }
@Override
public void write(byte[] data) {
System.out.println("[LOG] Writing " + data.length + " bytes");
wrapped.write(data);
}
@Override
public byte[] read() {
System.out.println("[LOG] Reading data");
byte[] data = wrapped.read();
System.out.println("[LOG] Read " + data.length + " bytes");
return data;
}
@Override
public String getDescription() {
return "Logged(" + wrapped.getDescription() + ")";
}
}
// 使用例 — デコレーターを自由に組み合わせ
DataStream stream = new LoggingDecorator(
new EncryptionDecorator(
new CompressionDecorator(
new FileDataStream("secret.dat")
),
"AES-256"
)
);
stream.write("Hello, World!".getBytes());
System.out.println("Pipeline: " + stream.getDescription());
// Pipeline: Logged(Encrypted[AES-256](Compressed(FileStream(secret.dat))))
Python実装(関数デコレーター)
import functools
import time
import logging
from typing import Callable, TypeVar, ParamSpec
P = ParamSpec("P")
R = TypeVar("R")
def retry(max_retries: int = 3, delay: float = 1.0):
"""リトライデコレーター"""
def decorator(func: Callable[P, R]) -> Callable[P, R]:
@functools.wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
last_exception = None
for attempt in range(1, max_retries + 1):
try:
return func(*args, **kwargs)
except Exception as e:
last_exception = e
logging.warning(
f"Attempt {attempt}/{max_retries} failed: {e}"
)
if attempt < max_retries:
time.sleep(delay * attempt)
raise last_exception
return wrapper
return decorator
def measure_time(func: Callable[P, R]) -> Callable[P, R]:
"""実行時間計測デコレーター"""
@functools.wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
logging.info(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper
def cache_result(ttl_seconds: int = 300):
"""TTL付きキャッシュデコレーター"""
cache: dict[str, tuple[float, object]] = {}
def decorator(func: Callable[P, R]) -> Callable[P, R]:
@functools.wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
key = str(args) + str(kwargs)
now = time.time()
if key in cache:
cached_time, cached_result = cache[key]
if now - cached_time < ttl_seconds:
logging.debug(f"Cache hit for {func.__name__}")
return cached_result
result = func(*args, **kwargs)
cache[key] = (now, result)
return result
return wrapper
return decorator
# デコレーターの組み合わせ
@measure_time
@retry(max_retries=3, delay=0.5)
@cache_result(ttl_seconds=60)
def fetch_user_data(user_id: int) -> dict:
"""外部APIからユーザーデータを取得"""
# API呼び出し(実際にはrequests.getなど)
return {"id": user_id, "name": f"User {user_id}"}
result = fetch_user_data(42)
3.5 Facade(ファサード)
目的
複雑なサブシステムに対する簡潔な統一インターフェースを提供する。サブシステムの複雑さを隠蔽し、クライアントが使いやすいAPIを提供する。
TypeScript実装
// 複雑なサブシステム群
class InventoryService {
checkStock(productId: string): boolean {
console.log(`Checking stock for product ${productId}`);
return true;
}
reserveStock(productId: string, quantity: number): string {
console.log(`Reserving ${quantity} of ${productId}`);
return `RSV-${Date.now()}`;
}
releaseReservation(reservationId: string): void {
console.log(`Releasing reservation ${reservationId}`);
}
}
class PaymentService {
authorize(amount: number, paymentMethod: string): string {
console.log(`Authorizing $${amount} via ${paymentMethod}`);
return `AUTH-${Date.now()}`;
}
capture(authorizationId: string): boolean {
console.log(`Capturing payment ${authorizationId}`);
return true;
}
void(authorizationId: string): void {
console.log(`Voiding authorization ${authorizationId}`);
}
}
class ShippingService {
calculateCost(address: string, weight: number): number {
console.log(`Calculating shipping to ${address}`);
return weight * 5.0;
}
createShipment(orderId: string, address: string): string {
console.log(`Creating shipment for order ${orderId}`);
return `SHIP-${Date.now()}`;
}
}
class NotificationService {
sendOrderConfirmation(email: string, orderId: string): void {
console.log(`Sending confirmation to ${email} for order ${orderId}`);
}
sendShippingNotification(email: string, trackingId: string): void {
console.log(`Sending shipping notification to ${email}`);
}
}
// Facade — 複雑な注文プロセスをシンプルなインターフェースで提供
class OrderFacade {
private inventory = new InventoryService();
private payment = new PaymentService();
private shipping = new ShippingService();
private notification = new NotificationService();
async placeOrder(order: {
productId: string;
quantity: number;
paymentMethod: string;
shippingAddress: string;
email: string;
}): Promise<{ orderId: string; trackingId: string }> {
console.log("=== Processing Order ===");
// 1. 在庫確認・予約
if (!this.inventory.checkStock(order.productId)) {
throw new Error("Out of stock");
}
const reservationId = this.inventory.reserveStock(
order.productId, order.quantity
);
try {
// 2. 送料計算
const shippingCost = this.shipping.calculateCost(
order.shippingAddress, order.quantity * 0.5
);
// 3. 決済
const totalAmount = order.quantity * 100 + shippingCost;
const authId = this.payment.authorize(totalAmount, order.paymentMethod);
const captured = this.payment.capture(authId);
if (!captured) {
this.inventory.releaseReservation(reservationId);
throw new Error("Payment failed");
}
// 4. 配送手配
const orderId = `ORD-${Date.now()}`;
const trackingId = this.shipping.createShipment(
orderId, order.shippingAddress
);
// 5. 通知
this.notification.sendOrderConfirmation(order.email, orderId);
return { orderId, trackingId };
} catch (error) {
this.inventory.releaseReservation(reservationId);
throw error;
}
}
}
// クライアントコードはシンプル
const facade = new OrderFacade();
facade.placeOrder({
productId: "PROD-001",
quantity: 2,
paymentMethod: "credit_card",
shippingAddress: "Tokyo, Japan",
email: "customer@example.com",
});
3.6 Flyweight(フライウェイト)
目的
多数の細粒度オブジェクトを効率的に共有し、メモリ使用量を削減する。
Python実装
class CharacterStyle:
"""Flyweight — 共有される内部状態"""
_cache: dict[tuple, "CharacterStyle"] = {}
def __new__(cls, font: str, size: int, color: str, bold: bool = False):
key = (font, size, color, bold)
if key not in cls._cache:
instance = super().__new__(cls)
instance.font = font
instance.size = size
instance.color = color
instance.bold = bold
cls._cache[key] = instance
return cls._cache[key]
def render(self, char: str, x: int, y: int) -> str:
weight = "bold" if self.bold else "normal"
return f"'{char}' at ({x},{y}) [{self.font} {self.size}px {self.color} {weight}]"
@classmethod
def get_cache_size(cls) -> int:
return len(cls._cache)
class TextEditor:
"""Context — Flyweightを使用するクライアント"""
def __init__(self):
self._characters: list[tuple[str, int, int, CharacterStyle]] = []
def add_char(self, char: str, x: int, y: int,
font: str, size: int, color: str, bold: bool = False):
style = CharacterStyle(font, size, color, bold)
self._characters.append((char, x, y, style))
def render(self) -> None:
for char, x, y, style in self._characters:
print(style.render(char, x, y))
def stats(self) -> str:
return (
f"Characters: {len(self._characters)}, "
f"Unique styles (Flyweights): {CharacterStyle.get_cache_size()}"
)
# 使用例 — 数千文字があっても、スタイルオブジェクトは数個で済む
editor = TextEditor()
text = "デザインパターンは重要です。"
for i, char in enumerate(text):
editor.add_char(char, i * 14, 0, "Noto Sans JP", 14, "#333333")
for i, char in enumerate("Design Patterns"):
editor.add_char(char, i * 12, 20, "Arial", 12, "#666666", bold=True)
print(editor.stats())
# Characters: 29, Unique styles (Flyweights): 2
3.7 Proxy(プロキシ)
目的
他のオブジェクトへのアクセスを制御する代理オブジェクトを提供する。遅延初期化、アクセス制御、ログ記録、キャッシュなどに利用される。
Java実装
// Subject
public interface ImageLoader {
Image load(String url);
ImageMetadata getMetadata(String url);
}
// RealSubject
public class RemoteImageLoader implements ImageLoader {
@Override
public Image load(String url) {
System.out.println("Downloading image from " + url);
// 実際のHTTPダウンロード
return downloadImage(url);
}
@Override
public ImageMetadata getMetadata(String url) {
return fetchMetadata(url);
}
}
// Proxy — キャッシュ+遅延読み込み+アクセスログ
public class CachingImageProxy implements ImageLoader {
private final ImageLoader realLoader;
private final Map<String, Image> cache = new ConcurrentHashMap<>();
private final Map<String, ImageMetadata> metadataCache = new ConcurrentHashMap<>();
private final long maxCacheSize;
public CachingImageProxy(ImageLoader realLoader, long maxCacheSize) {
this.realLoader = realLoader;
this.maxCacheSize = maxCacheSize;
}
@Override
public Image load(String url) {
return cache.computeIfAbsent(url, k -> {
System.out.println("[Proxy] Cache miss for " + k);
if (cache.size() >= maxCacheSize) {
evictOldest();
}
return realLoader.load(k);
});
}
@Override
public ImageMetadata getMetadata(String url) {
return metadataCache.computeIfAbsent(url, k -> realLoader.getMetadata(k));
}
private void evictOldest() {
// LRU的に最古のエントリを削除
String firstKey = cache.keySet().iterator().next();
cache.remove(firstKey);
System.out.println("[Proxy] Evicted " + firstKey);
}
public int getCacheSize() { return cache.size(); }
}
// Protection Proxy — アクセス制御
public class SecuredImageProxy implements ImageLoader {
private final ImageLoader realLoader;
private final AuthService authService;
public SecuredImageProxy(ImageLoader realLoader, AuthService authService) {
this.realLoader = realLoader;
this.authService = authService;
}
@Override
public Image load(String url) {
if (!authService.hasPermission("image:read")) {
throw new SecurityException("Permission denied: image:read");
}
return realLoader.load(url);
}
@Override
public ImageMetadata getMetadata(String url) {
if (!authService.hasPermission("image:metadata")) {
throw new SecurityException("Permission denied: image:metadata");
}
return realLoader.getMetadata(url);
}
}
TypeScript実装
// Subject
interface ApiClient {
get<T>(endpoint: string): Promise<T>;
post<T>(endpoint: string, body: unknown): Promise<T>;
}
// RealSubject
class HttpApiClient implements ApiClient {
constructor(private baseUrl: string) {}
async get<T>(endpoint: string): Promise<T> {
const res = await fetch(`${this.baseUrl}${endpoint}`);
return res.json() as Promise<T>;
}
async post<T>(endpoint: string, body: unknown): Promise<T> {
const res = await fetch(`${this.baseUrl}${endpoint}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
return res.json() as Promise<T>;
}
}
// Logging + Rate Limiting Proxy
class ApiClientProxy implements ApiClient {
private requestCount = 0;
private windowStart = Date.now();
constructor(
private readonly client: ApiClient,
private readonly rateLimit: number = 100,
private readonly windowMs: number = 60_000
) {}
async get<T>(endpoint: string): Promise<T> {
this.checkRateLimit();
console.log(`[API] GET ${endpoint}`);
const start = performance.now();
try {
const result = await this.client.get<T>(endpoint);
this.logDuration("GET", endpoint, start);
return result;
} catch (error) {
console.error(`[API] GET ${endpoint} FAILED`, error);
throw error;
}
}
async post<T>(endpoint: string, body: unknown): Promise<T> {
this.checkRateLimit();
console.log(`[API] POST ${endpoint}`);
const start = performance.now();
try {
const result = await this.client.post<T>(endpoint, body);
this.logDuration("POST", endpoint, start);
return result;
} catch (error) {
console.error(`[API] POST ${endpoint} FAILED`, error);
throw error;
}
}
private checkRateLimit(): void {
const now = Date.now();
if (now - this.windowStart > this.windowMs) {
this.requestCount = 0;
this.windowStart = now;
}
if (++this.requestCount > this.rateLimit) {
throw new Error("Rate limit exceeded");
}
}
private logDuration(method: string, endpoint: string, start: number): void {
const duration = (performance.now() - start).toFixed(2);
console.log(`[API] ${method} ${endpoint} completed in ${duration}ms`);
}
}
3.8 構造パターンのまとめ
| パターン | 目的 | 使いどころ |
|---|---|---|
| Adapter | 互換性のないインターフェースの接続 | レガシー統合、サードパーティ統一 |
| Bridge | 抽象と実装の分離 | プラットフォーム独立、出力形式切替 |
| Composite | ツリー構造の統一的な扱い | ファイルシステム、UI、組織図 |
| Decorator | 動的な機能追加 | I/Oストリーム、ミドルウェア |
| Facade | 複雑なサブシステムの簡素化 | 注文処理、API統合 |
| Flyweight | メモリ効率的なオブジェクト共有 | テキストレンダリング、キャッシュ |
| Proxy | アクセス制御・付加機能 | キャッシュ、認証、遅延読込 |
4. 振る舞いに関するパターン(Behavioral Patterns)
振る舞いに関するパターンは、オブジェクト間の責任分担や通信方法に焦点を当てる。アルゴリズムの選択、オブジェクト間のやり取り、責任の連鎖など、動的な振る舞いに関する設計手法を提供する。
4.1 Chain of Responsibility(責任の連鎖)
目的
リクエストの送信側と受信側を疎結合にし、複数のオブジェクトにリクエストの処理機会を与える。リクエストを処理できるオブジェクトが見つかるまで、チェーンに沿ってリクエストが渡される。
実際の使用例
- Webフレームワークのミドルウェアパイプライン(Express.js, Django)
- ログレベルフィルタリング
- 承認ワークフロー
Java実装
// Handler
public abstract class HttpMiddleware {
private HttpMiddleware next;
public HttpMiddleware setNext(HttpMiddleware next) {
this.next = next;
return next;
}
public void handle(HttpRequest request, HttpResponse response) {
if (next != null) {
next.handle(request, response);
}
}
}
// ConcreteHandlers
public class AuthenticationMiddleware extends HttpMiddleware {
@Override
public void handle(HttpRequest request, HttpResponse response) {
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
response.setStatus(401);
response.setBody("{\"error\": \"Unauthorized\"}");
return; // チェーンを中断
}
String jwt = token.substring(7);
try {
User user = JwtService.verify(jwt);
request.setAttribute("user", user);
super.handle(request, response); // 次へ
} catch (Exception e) {
response.setStatus(403);
response.setBody("{\"error\": \"Invalid token\"}");
}
}
}
public class RateLimitMiddleware extends HttpMiddleware {
private final Map<String, AtomicInteger> counters = new ConcurrentHashMap<>();
private final int maxRequests;
public RateLimitMiddleware(int maxRequests) {
this.maxRequests = maxRequests;
}
@Override
public void handle(HttpRequest request, HttpResponse response) {
String clientIp = request.getRemoteAddr();
AtomicInteger count = counters.computeIfAbsent(clientIp,
k -> new AtomicInteger(0));
if (count.incrementAndGet() > maxRequests) {
response.setStatus(429);
response.setBody("{\"error\": \"Too many requests\"}");
return;
}
super.handle(request, response);
}
}
public class LoggingMiddleware extends HttpMiddleware {
@Override
public void handle(HttpRequest request, HttpResponse response) {
long start = System.currentTimeMillis();
System.out.printf("[%s] %s %s%n",
Instant.now(), request.getMethod(), request.getPath());
super.handle(request, response);
long elapsed = System.currentTimeMillis() - start;
System.out.printf("[%s] %s %s -> %d (%dms)%n",
Instant.now(), request.getMethod(), request.getPath(),
response.getStatus(), elapsed);
}
}
public class RequestHandler extends HttpMiddleware {
@Override
public void handle(HttpRequest request, HttpResponse response) {
response.setStatus(200);
response.setBody("{\"message\": \"Success\"}");
}
}
// パイプラインの構築
HttpMiddleware pipeline = new LoggingMiddleware();
pipeline
.setNext(new RateLimitMiddleware(100))
.setNext(new AuthenticationMiddleware())
.setNext(new RequestHandler());
pipeline.handle(request, response);
Python実装
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional
@dataclass
class PurchaseRequest:
description: str
amount: float
requester: str
class Approver(ABC):
def __init__(self, name: str, approval_limit: float):
self.name = name
self.approval_limit = approval_limit
self._next: Optional[Approver] = None
def set_next(self, approver: "Approver") -> "Approver":
self._next = approver
return approver
def handle(self, request: PurchaseRequest) -> str:
if request.amount <= self.approval_limit:
return self.approve(request)
elif self._next:
print(f"{self.name}: 金額 ¥{request.amount:,.0f} は承認権限外 → 上位へ転送")
return self._next.handle(request)
else:
return f"❌ 購入要求「{request.description}」(¥{request.amount:,.0f}) は却下されました — 承認権限を持つ承認者がいません"
def approve(self, request: PurchaseRequest) -> str:
return f"✅ {self.name}が「{request.description}」(¥{request.amount:,.0f}) を承認しました"
class TeamLead(Approver):
def __init__(self):
super().__init__("チームリーダー", 100_000)
class Manager(Approver):
def __init__(self):
super().__init__("マネージャー", 500_000)
class Director(Approver):
def __init__(self):
super().__init__("ディレクター", 5_000_000)
class CEO(Approver):
def __init__(self):
super().__init__("CEO", 50_000_000)
# チェーンの構築
lead = TeamLead()
lead.set_next(Manager()).set_next(Director()).set_next(CEO())
# テスト
requests = [
PurchaseRequest("ノートPC", 80_000, "田中"),
PurchaseRequest("サーバー機器", 300_000, "佐藤"),
PurchaseRequest("オフィス移転", 3_000_000, "鈴木"),
PurchaseRequest("新規事業投資", 100_000_000, "高橋"),
]
for req in requests:
print(f"\n--- {req.requester}さんの購入要求: {req.description} ---")
result = lead.handle(req)
print(result)
4.2 Command(コマンド)
目的
リクエストをオブジェクトとしてカプセル化し、パラメータ化、キューイング、ロギング、アンドゥ操作を可能にする。
TypeScript実装
// Command Interface
interface Command {
execute(): void;
undo(): void;
getDescription(): string;
}
// Receiver
class TextDocument {
private content: string[] = [];
private cursorPosition = 0;
insert(text: string, position: number): void {
this.content.splice(position, 0, text);
this.cursorPosition = position + 1;
}
delete(position: number, length: number): string {
const deleted = this.content.splice(position, length);
this.cursorPosition = position;
return deleted.join("");
}
getText(): string {
return this.content.join("");
}
getCursorPosition(): number {
return this.cursorPosition;
}
}
// ConcreteCommands
class InsertTextCommand implements Command {
private insertedAt: number;
constructor(
private document: TextDocument,
private text: string,
private position: number
) {
this.insertedAt = position;
}
execute(): void {
for (const char of this.text) {
this.document.insert(char, this.insertedAt++);
}
}
undo(): void {
this.document.delete(this.position, this.text.length);
}
getDescription(): string {
return `Insert "${this.text}" at position ${this.position}`;
}
}
class DeleteTextCommand implements Command {
private deletedText = "";
constructor(
private document: TextDocument,
private position: number,
private length: number
) {}
execute(): void {
this.deletedText = this.document.delete(this.position, this.length);
}
undo(): void {
let pos = this.position;
for (const char of this.deletedText) {
this.document.insert(char, pos++);
}
}
getDescription(): string {
return `Delete ${this.length} chars at position ${this.position}`;
}
}
// Invoker — コマンド履歴管理
class CommandHistory {
private undoStack: Command[] = [];
private redoStack: Command[] = [];
execute(command: Command): void {
command.execute();
this.undoStack.push(command);
this.redoStack = []; // 新しいコマンド実行時はRedoスタックをクリア
}
undo(): void {
const command = this.undoStack.pop();
if (command) {
command.undo();
this.redoStack.push(command);
console.log(`Undo: ${command.getDescription()}`);
}
}
redo(): void {
const command = this.redoStack.pop();
if (command) {
command.execute();
this.undoStack.push(command);
console.log(`Redo: ${command.getDescription()}`);
}
}
getHistory(): string[] {
return this.undoStack.map((c) => c.getDescription());
}
}
// 使用例
const doc = new TextDocument();
const history = new CommandHistory();
history.execute(new InsertTextCommand(doc, "Hello", 0));
history.execute(new InsertTextCommand(doc, " World", 5));
console.log(doc.getText()); // "Hello World"
history.undo(); // Undo: Insert " World"
console.log(doc.getText()); // "Hello"
history.redo(); // Redo: Insert " World"
console.log(doc.getText()); // "Hello World"
4.3 Iterator(イテレーター)
目的
コレクションの内部構造を公開せずに、その要素に順次アクセスする方法を提供する。
Python実装
from typing import Iterator, Generic, TypeVar
T = TypeVar("T")
class TreeNode(Generic[T]):
def __init__(self, value: T):
self.value = value
self.left: TreeNode[T] | None = None
self.right: TreeNode[T] | None = None
class BinaryTree(Generic[T]):
def __init__(self, root: TreeNode[T] | None = None):
self.root = root
def inorder(self) -> Iterator[T]:
"""中間順(昇順)走査"""
def _inorder(node: TreeNode[T] | None):
if node:
yield from _inorder(node.left)
yield node.value
yield from _inorder(node.right)
return _inorder(self.root)
def preorder(self) -> Iterator[T]:
"""前順走査"""
def _preorder(node: TreeNode[T] | None):
if node:
yield node.value
yield from _preorder(node.left)
yield from _preorder(node.right)
return _preorder(self.root)
def level_order(self) -> Iterator[T]:
"""幅優先走査"""
from collections import deque
if not self.root:
return
queue = deque([self.root])
while queue:
node = queue.popleft()
yield node.value
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
def __iter__(self) -> Iterator[T]:
"""デフォルトは中間順"""
return self.inorder()
# 使用例
root = TreeNode(5)
root.left = TreeNode(3)
root.right = TreeNode(8)
root.left.left = TreeNode(1)
root.left.right = TreeNode(4)
root.right.left = TreeNode(7)
tree = BinaryTree(root)
print("Inorder: ", list(tree.inorder())) # [1, 3, 4, 5, 7, 8]
print("Preorder: ", list(tree.preorder())) # [5, 3, 1, 4, 8, 7]
print("Level order:", list(tree.level_order())) # [5, 3, 8, 1, 4, 7]
# for文で直接使用可能
for value in tree:
print(value, end=" ")
4.4 Mediator(メディエーター)
目的
オブジェクト間の複雑な通信を仲介者(Mediator)に集約し、オブジェクト同士の直接的な依存を排除する。
Java実装
// Mediator
public interface ChatRoomMediator {
void sendMessage(String message, User sender);
void addUser(User user);
void removeUser(User user);
}
// ConcreteMediator
public class ChatRoom implements ChatRoomMediator {
private final String name;
private final Set<User> users = new HashSet<>();
private final List<ChatMessage> history = new ArrayList<>();
public ChatRoom(String name) { this.name = name; }
@Override
public void sendMessage(String message, User sender) {
ChatMessage chatMsg = new ChatMessage(sender.getName(), message, Instant.now());
history.add(chatMsg);
// 送信者以外の全ユーザーに配信
users.stream()
.filter(u -> !u.equals(sender))
.forEach(u -> u.receive(chatMsg));
}
@Override
public void addUser(User user) {
users.add(user);
sendMessage(user.getName() + " が入室しました", new SystemUser());
}
@Override
public void removeUser(User user) {
users.remove(user);
sendMessage(user.getName() + " が退室しました", new SystemUser());
}
public List<ChatMessage> getHistory() { return List.copyOf(history); }
}
// Colleague
public abstract class User {
protected ChatRoomMediator chatRoom;
protected String name;
public User(String name, ChatRoomMediator chatRoom) {
this.name = name;
this.chatRoom = chatRoom;
}
public String getName() { return name; }
public void send(String message) {
System.out.printf("[%s] 送信: %s%n", name, message);
chatRoom.sendMessage(message, this);
}
public abstract void receive(ChatMessage message);
}
public class RegularUser extends User {
public RegularUser(String name, ChatRoomMediator chatRoom) {
super(name, chatRoom);
}
@Override
public void receive(ChatMessage message) {
System.out.printf("[%s] 受信 from %s: %s%n",
name, message.sender(), message.content());
}
}
// 使用例
ChatRoom room = new ChatRoom("general");
User alice = new RegularUser("Alice", room);
User bob = new RegularUser("Bob", room);
User charlie = new RegularUser("Charlie", room);
room.addUser(alice);
room.addUser(bob);
room.addUser(charlie);
alice.send("こんにちは!");
bob.send("やあ、Alice!");
4.5 Memento(メメント)
目的
オブジェクトの内部状態をカプセル化を破壊せずに外部に保存し、後からその状態に復元できるようにする。
Python実装
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime
from copy import deepcopy
@dataclass(frozen=True)
class EditorMemento:
"""Memento — エディターの状態スナップショット"""
content: str
cursor_position: int
selection_start: int | None
selection_end: int | None
timestamp: datetime = field(default_factory=datetime.now)
def get_description(self) -> str:
preview = self.content[:50] + "..." if len(self.content) > 50 else self.content
return f"[{self.timestamp:%H:%M:%S}] cursor={self.cursor_position} \"{preview}\""
class TextEditor:
"""Originator — 状態を保存・復元するオブジェクト"""
def __init__(self):
self._content = ""
self._cursor = 0
self._selection_start: int | None = None
self._selection_end: int | None = None
def type_text(self, text: str) -> None:
self._content = (
self._content[:self._cursor] + text + self._content[self._cursor:]
)
self._cursor += len(text)
def delete(self, count: int = 1) -> None:
start = max(0, self._cursor - count)
self._content = self._content[:start] + self._content[self._cursor:]
self._cursor = start
def move_cursor(self, position: int) -> None:
self._cursor = max(0, min(position, len(self._content)))
def save(self) -> EditorMemento:
return EditorMemento(
content=self._content,
cursor_position=self._cursor,
selection_start=self._selection_start,
selection_end=self._selection_end,
)
def restore(self, memento: EditorMemento) -> None:
self._content = memento.content
self._cursor = memento.cursor_position
self._selection_start = memento.selection_start
self._selection_end = memento.selection_end
def __str__(self) -> str:
return f'"{self._content}" (cursor: {self._cursor})'
class EditorHistory:
"""Caretaker — Mementoの管理"""
def __init__(self, editor: TextEditor):
self._editor = editor
self._undo_stack: list[EditorMemento] = []
self._redo_stack: list[EditorMemento] = []
def save(self) -> None:
self._undo_stack.append(self._editor.save())
self._redo_stack.clear()
def undo(self) -> bool:
if not self._undo_stack:
return False
memento = self._undo_stack.pop()
self._redo_stack.append(self._editor.save())
self._editor.restore(memento)
return True
def redo(self) -> bool:
if not self._redo_stack:
return False
memento = self._redo_stack.pop()
self._undo_stack.append(self._editor.save())
self._editor.restore(memento)
return True
def show_history(self) -> None:
print("--- Undo History ---")
for i, m in enumerate(self._undo_stack):
print(f" {i}: {m.get_description()}")
# 使用例
editor = TextEditor()
history = EditorHistory(editor)
history.save()
editor.type_text("デザインパターン")
print(editor) # "デザインパターン" (cursor: 8)
history.save()
editor.type_text("は重要")
print(editor) # "デザインパターンは重要" (cursor: 11)
history.undo()
print(editor) # "デザインパターン" (cursor: 8)
history.undo()
print(editor) # "" (cursor: 0)
history.redo()
print(editor) # "デザインパターン" (cursor: 8)
4.6 Observer(オブザーバー)
目的
オブジェクト間に一対多の依存関係を定義し、あるオブジェクトの状態変化時に、依存するすべてのオブジェクトに自動的に通知する。
実際の使用例
- イベントリスナー(DOM Events, Java AWT/Swing)
- Reactive Extensions(RxJava, RxJS)
- Pub/Subメッセージングシステム
Java実装
// 型安全なObserverパターン(Generics使用)
public interface EventListener<T> {
void onEvent(T event);
}
public class EventBus<T> {
private final Map<Class<?>, List<EventListener<?>>> listeners = new ConcurrentHashMap<>();
public <E extends T> void subscribe(Class<E> eventType, EventListener<E> listener) {
listeners.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>())
.add(listener);
}
public <E extends T> void unsubscribe(Class<E> eventType, EventListener<E> listener) {
List<EventListener<?>> list = listeners.get(eventType);
if (list != null) {
list.remove(listener);
}
}
@SuppressWarnings("unchecked")
public <E extends T> void publish(E event) {
List<EventListener<?>> list = listeners.get(event.getClass());
if (list != null) {
for (EventListener<?> listener : list) {
((EventListener<E>) listener).onEvent(event);
}
}
}
}
// イベント定義
public sealed interface DomainEvent permits OrderCreated, OrderShipped, OrderCancelled {
String orderId();
Instant timestamp();
}
public record OrderCreated(String orderId, String customerId, BigDecimal total,
Instant timestamp) implements DomainEvent {}
public record OrderShipped(String orderId, String trackingNumber,
Instant timestamp) implements DomainEvent {}
public record OrderCancelled(String orderId, String reason,
Instant timestamp) implements DomainEvent {}
// リスナー
public class InventoryService implements EventListener<OrderCreated> {
@Override
public void onEvent(OrderCreated event) {
System.out.println("Inventory: Reserving stock for order " + event.orderId());
}
}
public class NotificationService implements EventListener<OrderShipped> {
@Override
public void onEvent(OrderShipped event) {
System.out.println("Notification: Sending shipping email for " + event.orderId()
+ " tracking: " + event.trackingNumber());
}
}
// 使用例
EventBus<DomainEvent> eventBus = new EventBus<>();
eventBus.subscribe(OrderCreated.class, new InventoryService());
eventBus.subscribe(OrderShipped.class, new NotificationService());
eventBus.publish(new OrderCreated("ORD-001", "CUST-001",
new BigDecimal("15000"), Instant.now()));
eventBus.publish(new OrderShipped("ORD-001", "TRACK-ABC123", Instant.now()));
TypeScript実装
type EventCallback<T = unknown> = (data: T) => void;
class TypedEventEmitter {
private listeners = new Map<string, Set<EventCallback>>();
on<T>(event: string, callback: EventCallback<T>): () => void {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.add(callback as EventCallback);
// unsubscribe関数を返す
return () => {
this.listeners.get(event)?.delete(callback as EventCallback);
};
}
emit<T>(event: string, data: T): void {
this.listeners.get(event)?.forEach((cb) => cb(data));
}
}
// 使用例 — Stock Price Observer
interface PriceUpdate {
symbol: string;
price: number;
change: number;
timestamp: Date;
}
const priceEmitter = new TypedEventEmitter();
// 購読
const unsub1 = priceEmitter.on<PriceUpdate>("price:AAPL", (data) => {
console.log(`Dashboard: AAPL = $${data.price} (${data.change > 0 ? "+" : ""}${data.change}%)`);
});
const unsub2 = priceEmitter.on<PriceUpdate>("price:AAPL", (data) => {
if (Math.abs(data.change) > 5) {
console.log(`ALERT: AAPL significant move: ${data.change}%`);
}
});
// 配信
priceEmitter.emit<PriceUpdate>("price:AAPL", {
symbol: "AAPL",
price: 185.5,
change: 2.3,
timestamp: new Date(),
});
// 購読解除
unsub1();
4.7 State(ステート)
目的
オブジェクトの内部状態が変化したときに、そのオブジェクトの振る舞いを変更する。あたかもオブジェクトのクラスが変わったかのように見せる。
Java実装
// State Interface
public interface OrderState {
void next(OrderContext order);
void previous(OrderContext order);
void cancel(OrderContext order);
String getStatus();
}
// Context
public class OrderContext {
private OrderState state;
private final String orderId;
public OrderContext(String orderId) {
this.orderId = orderId;
this.state = new PendingState();
}
public void setState(OrderState state) {
System.out.printf("Order %s: %s -> %s%n",
orderId, this.state.getStatus(), state.getStatus());
this.state = state;
}
public void next() { state.next(this); }
public void previous() { state.previous(this); }
public void cancel() { state.cancel(this); }
public String getStatus() { return state.getStatus(); }
}
// Concrete States
public class PendingState implements OrderState {
@Override
public void next(OrderContext order) {
order.setState(new ProcessingState());
}
@Override
public void previous(OrderContext order) {
System.out.println("最初の状態です。前の状態はありません。");
}
@Override
public void cancel(OrderContext order) {
order.setState(new CancelledState());
}
@Override
public String getStatus() { return "PENDING"; }
}
public class ProcessingState implements OrderState {
@Override
public void next(OrderContext order) {
order.setState(new ShippedState());
}
@Override
public void previous(OrderContext order) {
order.setState(new PendingState());
}
@Override
public void cancel(OrderContext order) {
order.setState(new CancelledState());
}
@Override
public String getStatus() { return "PROCESSING"; }
}
public class ShippedState implements OrderState {
@Override
public void next(OrderContext order) {
order.setState(new DeliveredState());
}
@Override
public void previous(OrderContext order) {
order.setState(new ProcessingState());
}
@Override
public void cancel(OrderContext order) {
System.out.println("発送済みの注文はキャンセルできません。");
}
@Override
public String getStatus() { return "SHIPPED"; }
}
public class DeliveredState implements OrderState {
@Override
public void next(OrderContext order) {
System.out.println("配達済みです。次の状態はありません。");
}
@Override
public void previous(OrderContext order) {
System.out.println("配達済みの注文は戻せません。");
}
@Override
public void cancel(OrderContext order) {
System.out.println("配達済みの注文はキャンセルできません。");
}
@Override
public String getStatus() { return "DELIVERED"; }
}
public class CancelledState implements OrderState {
@Override
public void next(OrderContext order) {
System.out.println("キャンセル済みの注文は処理できません。");
}
@Override
public void previous(OrderContext order) {
System.out.println("キャンセル済みの注文は戻せません。");
}
@Override
public void cancel(OrderContext order) {
System.out.println("すでにキャンセル済みです。");
}
@Override
public String getStatus() { return "CANCELLED"; }
}
// 使用例
OrderContext order = new OrderContext("ORD-001");
order.next(); // PENDING -> PROCESSING
order.next(); // PROCESSING -> SHIPPED
order.cancel(); // 発送済みの注文はキャンセルできません
order.next(); // SHIPPED -> DELIVERED
4.8 Strategy(ストラテジー)
目的
アルゴリズムのファミリーを定義し、それぞれをカプセル化して交換可能にする。アルゴリズムをクライアントから独立に変更できるようにする。
Python実装
from abc import ABC, abstractmethod
from dataclasses import dataclass
@dataclass
class ShippingOrder:
weight_kg: float
distance_km: float
is_fragile: bool
destination_country: str
class ShippingStrategy(ABC):
@abstractmethod
def calculate_cost(self, order: ShippingOrder) -> float:
pass
@abstractmethod
def estimate_days(self, order: ShippingOrder) -> int:
pass
@abstractmethod
def get_name(self) -> str:
pass
class StandardShipping(ShippingStrategy):
def calculate_cost(self, order: ShippingOrder) -> float:
base = order.weight_kg * 100 + order.distance_km * 0.5
if order.is_fragile:
base *= 1.2
return base
def estimate_days(self, order: ShippingOrder) -> int:
return max(3, int(order.distance_km / 500))
def get_name(self) -> str:
return "通常配送"
class ExpressShipping(ShippingStrategy):
def calculate_cost(self, order: ShippingOrder) -> float:
base = order.weight_kg * 200 + order.distance_km * 1.5
if order.is_fragile:
base *= 1.3
return base
def estimate_days(self, order: ShippingOrder) -> int:
return max(1, int(order.distance_km / 1500))
def get_name(self) -> str:
return "速達配送"
class InternationalShipping(ShippingStrategy):
COUNTRY_MULTIPLIER = {"US": 1.5, "EU": 1.3, "CN": 1.2}
def calculate_cost(self, order: ShippingOrder) -> float:
base = order.weight_kg * 500 + order.distance_km * 2.0
multiplier = self.COUNTRY_MULTIPLIER.get(order.destination_country, 1.8)
return base * multiplier
def estimate_days(self, order: ShippingOrder) -> int:
return max(5, int(order.distance_km / 300))
def get_name(self) -> str:
return "国際配送"
# Context
class ShippingCalculator:
def __init__(self, strategy: ShippingStrategy):
self._strategy = strategy
def set_strategy(self, strategy: ShippingStrategy) -> None:
self._strategy = strategy
def calculate(self, order: ShippingOrder) -> dict:
cost = self._strategy.calculate_cost(order)
days = self._strategy.estimate_days(order)
return {
"method": self._strategy.get_name(),
"cost": f"¥{cost:,.0f}",
"estimated_days": f"{days}日",
}
# 使用例
order = ShippingOrder(weight_kg=5.0, distance_km=800,
is_fragile=True, destination_country="JP")
calculator = ShippingCalculator(StandardShipping())
print(calculator.calculate(order))
calculator.set_strategy(ExpressShipping())
print(calculator.calculate(order))
4.9 Template Method(テンプレートメソッド)
目的
アルゴリズムの骨格を定義し、一部のステップをサブクラスに委ねる。アルゴリズムの構造を変えずに、特定のステップを再定義できるようにする。
Java実装
public abstract class DataPipeline<T, R> {
// Template Method — 処理の流れを定義
public final List<R> process() {
List<T> rawData = extract();
List<T> validData = validate(rawData);
List<R> transformed = transform(validData);
load(transformed);
return transformed;
}
// 抽象メソッド — サブクラスで実装
protected abstract List<T> extract();
protected abstract List<R> transform(List<T> data);
protected abstract void load(List<R> data);
// フック — デフォルト実装あり、必要に応じてオーバーライド
protected List<T> validate(List<T> data) {
return data.stream()
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}
// CSV → JSON変換パイプライン
public class CsvToJsonPipeline extends DataPipeline<String[], Map<String, String>> {
private final String filePath;
private String[] headers;
public CsvToJsonPipeline(String filePath) {
this.filePath = filePath;
}
@Override
protected List<String[]> extract() {
System.out.println("Extracting from CSV: " + filePath);
List<String[]> rows = new ArrayList<>();
// CSV読み込みロジック
headers = new String[]{"id", "name", "email"};
rows.add(new String[]{"1", "田中太郎", "tanaka@example.com"});
rows.add(new String[]{"2", "佐藤花子", "sato@example.com"});
return rows;
}
@Override
protected List<String[]> validate(List<String[]> data) {
return data.stream()
.filter(row -> row.length == headers.length)
.filter(row -> row[2].contains("@"))
.collect(Collectors.toList());
}
@Override
protected List<Map<String, String>> transform(List<String[]> data) {
return data.stream()
.map(row -> {
Map<String, String> map = new HashMap<>();
for (int i = 0; i < headers.length; i++) {
map.put(headers[i], row[i]);
}
return map;
})
.collect(Collectors.toList());
}
@Override
protected void load(List<Map<String, String>> data) {
System.out.println("Loading " + data.size() + " records to JSON file");
// JSON書き出し
}
}
// 使用例
DataPipeline<String[], Map<String, String>> pipeline =
new CsvToJsonPipeline("users.csv");
List<Map<String, String>> result = pipeline.process();
4.10 Visitor(ビジター)
目的
オブジェクト構造の要素に対して実行する操作を、要素のクラスを変更せずに新しく定義できるようにする。
TypeScript実装
// Element
interface ASTNode {
accept(visitor: ASTVisitor): unknown;
}
class NumberLiteral implements ASTNode {
constructor(public value: number) {}
accept(visitor: ASTVisitor) { return visitor.visitNumber(this); }
}
class StringLiteral implements ASTNode {
constructor(public value: string) {}
accept(visitor: ASTVisitor) { return visitor.visitString(this); }
}
class BinaryExpression implements ASTNode {
constructor(
public left: ASTNode,
public operator: string,
public right: ASTNode
) {}
accept(visitor: ASTVisitor) { return visitor.visitBinaryExpr(this); }
}
class FunctionCall implements ASTNode {
constructor(
public name: string,
public args: ASTNode[]
) {}
accept(visitor: ASTVisitor) { return visitor.visitFunctionCall(this); }
}
// Visitor
interface ASTVisitor {
visitNumber(node: NumberLiteral): unknown;
visitString(node: StringLiteral): unknown;
visitBinaryExpr(node: BinaryExpression): unknown;
visitFunctionCall(node: FunctionCall): unknown;
}
// 評価Visitor
class Evaluator implements ASTVisitor {
visitNumber(node: NumberLiteral): number {
return node.value;
}
visitString(node: StringLiteral): string {
return node.value;
}
visitBinaryExpr(node: BinaryExpression): number {
const left = node.left.accept(this) as number;
const right = node.right.accept(this) as number;
switch (node.operator) {
case "+": return left + right;
case "-": return left - right;
case "*": return left * right;
case "/": return left / right;
default: throw new Error(`Unknown operator: ${node.operator}`);
}
}
visitFunctionCall(node: FunctionCall): unknown {
const args = node.args.map((a) => a.accept(this));
const builtins: Record<string, (...args: any[]) => any> = {
max: Math.max,
min: Math.min,
abs: Math.abs,
};
const fn = builtins[node.name];
if (!fn) throw new Error(`Unknown function: ${node.name}`);
return fn(...args);
}
}
// 整形表示Visitor
class PrettyPrinter implements ASTVisitor {
visitNumber(node: NumberLiteral): string {
return String(node.value);
}
visitString(node: StringLiteral): string {
return `"${node.value}"`;
}
visitBinaryExpr(node: BinaryExpression): string {
const left = node.left.accept(this);
const right = node.right.accept(this);
return `(${left} ${node.operator} ${right})`;
}
visitFunctionCall(node: FunctionCall): string {
const args = node.args.map((a) => a.accept(this)).join(", ");
return `${node.name}(${args})`;
}
}
// 使用例: max(3 + 4, 10 - 2) を表現
const ast = new FunctionCall("max", [
new BinaryExpression(new NumberLiteral(3), "+", new NumberLiteral(4)),
new BinaryExpression(new NumberLiteral(10), "-", new NumberLiteral(2)),
]);
const evaluator = new Evaluator();
const printer = new PrettyPrinter();
console.log(ast.accept(printer)); // "max((3 + 4), (10 - 2))"
console.log(ast.accept(evaluator)); // 8
4.11 振る舞いパターンのまとめ
| パターン | 目的 | 使いどころ |
|---|---|---|
| Chain of Responsibility | リクエストをチェーンで処理 | ミドルウェア、承認フロー |
| Command | リクエストのオブジェクト化 | Undo/Redo、タスクキュー |
| Iterator | コレクションの順次アクセス | ツリー走査、カスタムコレクション |
| Mediator | オブジェクト間通信の集約 | チャットルーム、UIコンポーネント連携 |
| Memento | 状態の保存と復元 | エディター、ゲームのセーブ |
| Observer | 状態変化の通知 | イベント駆動、Pub/Sub |
| State | 状態に応じた振る舞いの変更 | 注文ステータス、ワークフロー |
| Strategy | アルゴリズムの交換可能性 | ソート、配送料計算、認証方式 |
| Template Method | アルゴリズムの骨格定義 | ETLパイプライン、テストフレームワーク |
| Visitor | 構造を変えずに操作を追加 | AST処理、レポート生成 |
5. モダンなデザインパターン
GoFパターンが1994年に発表されてから30年以上が経過した。マイクロサービス、クラウドネイティブ、分散システムの普及に伴い、新しい設計課題に対応するパターンが生まれてきた。本章では、現代のソフトウェア開発で広く使われるモダンパターンを解説する。
5.1 Dependency Injection(依存性注入 / DI)
目的
オブジェクトの依存関係を外部から注入し、疎結合な設計を実現する。テスト容易性と柔軟性を大幅に向上させる。
注入方式の種類
| 方式 | 特徴 |
|---|---|
| コンストラクタ注入 | 最も推奨。不変性を保証できる |
| セッター注入 | オプショナルな依存に使用 |
| インターフェース注入 | フレームワークが提供するインターフェースを通じた注入 |
Java実装(手動DI)
// インターフェース定義
public interface UserRepository {
Optional<User> findById(String id);
List<User> findAll();
void save(User user);
}
public interface EmailService {
void send(String to, String subject, String body);
}
public interface PasswordEncoder {
String encode(String rawPassword);
boolean matches(String rawPassword, String encodedPassword);
}
// 実装クラス
public class PostgresUserRepository implements UserRepository {
private final DataSource dataSource;
public PostgresUserRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Optional<User> findById(String id) {
// JDBCによるクエリ実行
return Optional.empty();
}
@Override
public List<User> findAll() { return List.of(); }
@Override
public void save(User user) { /* INSERT/UPDATE */ }
}
// コンストラクタ注入
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
private final PasswordEncoder passwordEncoder;
// 全ての依存がコンストラクタで注入される
public UserService(UserRepository userRepository,
EmailService emailService,
PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.emailService = emailService;
this.passwordEncoder = passwordEncoder;
}
public User register(String name, String email, String rawPassword) {
String encoded = passwordEncoder.encode(rawPassword);
User user = new User(UUID.randomUUID().toString(), name, email, encoded);
userRepository.save(user);
emailService.send(email, "Welcome", "ご登録ありがとうございます。");
return user;
}
}
// テストでモックを注入
public class UserServiceTest {
@Test
void register_shouldSaveUserAndSendEmail() {
// モックの作成
UserRepository mockRepo = mock(UserRepository.class);
EmailService mockEmail = mock(EmailService.class);
PasswordEncoder mockEncoder = mock(PasswordEncoder.class);
when(mockEncoder.encode("password")).thenReturn("hashed");
// モックを注入
UserService service = new UserService(mockRepo, mockEmail, mockEncoder);
User result = service.register("太郎", "taro@test.com", "password");
verify(mockRepo).save(any(User.class));
verify(mockEmail).send(eq("taro@test.com"), anyString(), anyString());
}
}
Python実装
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Protocol
# Protocol(構造的サブタイピング)を使ったDI
class NotificationSender(Protocol):
def send(self, recipient: str, message: str) -> bool: ...
class MetricsCollector(Protocol):
def increment(self, metric: str, tags: dict[str, str] | None = None) -> None: ...
# 実装
class SlackNotifier:
def __init__(self, webhook_url: str):
self.webhook_url = webhook_url
def send(self, recipient: str, message: str) -> bool:
print(f"Slack to {recipient}: {message}")
return True
class DatadogMetrics:
def __init__(self, api_key: str):
self.api_key = api_key
def increment(self, metric: str, tags: dict[str, str] | None = None) -> None:
print(f"Datadog: {metric} +1 tags={tags}")
# DI対象のサービス
class DeploymentService:
def __init__(
self,
notifier: NotificationSender,
metrics: MetricsCollector,
):
self._notifier = notifier
self._metrics = metrics
def deploy(self, service_name: str, version: str) -> bool:
self._metrics.increment("deploy.started", {"service": service_name})
# デプロイロジック...
success = True
if success:
self._notifier.send(
"#deployments",
f"{service_name} v{version} deployed successfully"
)
self._metrics.increment("deploy.success", {"service": service_name})
return success
# 依存の組み立て(Composition Root)
def create_deployment_service() -> DeploymentService:
notifier = SlackNotifier("https://hooks.slack.com/...")
metrics = DatadogMetrics("dd-api-key-xxx")
return DeploymentService(notifier=notifier, metrics=metrics)
# テスト時はモックを注入
class FakeNotifier:
def __init__(self):
self.messages: list[tuple[str, str]] = []
def send(self, recipient: str, message: str) -> bool:
self.messages.append((recipient, message))
return True
class FakeMetrics:
def __init__(self):
self.counts: dict[str, int] = {}
def increment(self, metric: str, tags: dict[str, str] | None = None) -> None:
self.counts[metric] = self.counts.get(metric, 0) + 1
def test_deploy():
notifier = FakeNotifier()
metrics = FakeMetrics()
service = DeploymentService(notifier=notifier, metrics=metrics)
result = service.deploy("api-server", "2.1.0")
assert result is True
assert len(notifier.messages) == 1
assert metrics.counts["deploy.success"] == 1
TypeScript実装(DIコンテナ)
// シンプルなDIコンテナ
class Container {
private bindings = new Map<string, () => unknown>();
private singletons = new Map<string, unknown>();
bind<T>(token: string, factory: () => T): void {
this.bindings.set(token, factory);
}
singleton<T>(token: string, factory: () => T): void {
this.bindings.set(token, () => {
if (!this.singletons.has(token)) {
this.singletons.set(token, factory());
}
return this.singletons.get(token);
});
}
resolve<T>(token: string): T {
const factory = this.bindings.get(token);
if (!factory) throw new Error(`No binding for: ${token}`);
return factory() as T;
}
}
// インターフェース
interface CacheService {
get(key: string): Promise<string | null>;
set(key: string, value: string, ttl?: number): Promise<void>;
}
interface Logger {
info(message: string, meta?: Record<string, unknown>): void;
error(message: string, meta?: Record<string, unknown>): void;
}
// 実装
class RedisCacheService implements CacheService {
constructor(private connectionString: string) {}
async get(key: string): Promise<string | null> { return null; }
async set(key: string, value: string, ttl?: number): Promise<void> {}
}
class WinstonLogger implements Logger {
info(message: string, meta?: Record<string, unknown>): void {
console.log(`[INFO] ${message}`, meta);
}
error(message: string, meta?: Record<string, unknown>): void {
console.error(`[ERROR] ${message}`, meta);
}
}
// コンテナの設定
const container = new Container();
container.singleton<Logger>("Logger", () => new WinstonLogger());
container.singleton<CacheService>("Cache",
() => new RedisCacheService("redis://localhost:6379"));
// 使用
const logger = container.resolve<Logger>("Logger");
const cache = container.resolve<CacheService>("Cache");
5.2 Repository(リポジトリ)
目的
データアクセスロジックをビジネスロジックから分離し、コレクション風のインターフェースでデータソースへアクセスする。
TypeScript実装
// エンティティ
interface Product {
id: string;
name: string;
price: number;
category: string;
stock: number;
createdAt: Date;
}
// Repository Interface
interface ProductRepository {
findById(id: string): Promise<Product | null>;
findByCategory(category: string): Promise<Product[]>;
findInStock(): Promise<Product[]>;
save(product: Product): Promise<void>;
delete(id: string): Promise<void>;
count(): Promise<number>;
}
// インメモリ実装(テスト用)
class InMemoryProductRepository implements ProductRepository {
private store = new Map<string, Product>();
async findById(id: string): Promise<Product | null> {
return this.store.get(id) ?? null;
}
async findByCategory(category: string): Promise<Product[]> {
return [...this.store.values()].filter(
(p) => p.category === category
);
}
async findInStock(): Promise<Product[]> {
return [...this.store.values()].filter((p) => p.stock > 0);
}
async save(product: Product): Promise<void> {
this.store.set(product.id, { ...product });
}
async delete(id: string): Promise<void> {
this.store.delete(id);
}
async count(): Promise<number> {
return this.store.size;
}
}
// PostgreSQL実装(本番用)
class PostgresProductRepository implements ProductRepository {
constructor(private pool: any /* pg.Pool */) {}
async findById(id: string): Promise<Product | null> {
const { rows } = await this.pool.query(
"SELECT * FROM products WHERE id = $1", [id]
);
return rows[0] ? this.mapToEntity(rows[0]) : null;
}
async findByCategory(category: string): Promise<Product[]> {
const { rows } = await this.pool.query(
"SELECT * FROM products WHERE category = $1 ORDER BY name", [category]
);
return rows.map(this.mapToEntity);
}
async findInStock(): Promise<Product[]> {
const { rows } = await this.pool.query(
"SELECT * FROM products WHERE stock > 0 ORDER BY name"
);
return rows.map(this.mapToEntity);
}
async save(product: Product): Promise<void> {
await this.pool.query(
`INSERT INTO products (id, name, price, category, stock, created_at)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (id) DO UPDATE SET
name = $2, price = $3, category = $4, stock = $5`,
[product.id, product.name, product.price,
product.category, product.stock, product.createdAt]
);
}
async delete(id: string): Promise<void> {
await this.pool.query("DELETE FROM products WHERE id = $1", [id]);
}
async count(): Promise<number> {
const { rows } = await this.pool.query("SELECT COUNT(*) FROM products");
return parseInt(rows[0].count);
}
private mapToEntity(row: any): Product {
return {
id: row.id,
name: row.name,
price: parseFloat(row.price),
category: row.category,
stock: row.stock,
createdAt: row.created_at,
};
}
}
5.3 CQRS(Command Query Responsibility Segregation)
目的
読み取り(Query)と書き込み(Command)の責務を分離し、それぞれを独立にスケーリング・最適化できるようにする。
Java実装
// Command側
public sealed interface OrderCommand permits CreateOrderCommand, CancelOrderCommand {}
public record CreateOrderCommand(
String customerId,
List<OrderItem> items,
String shippingAddress
) implements OrderCommand {}
public record CancelOrderCommand(
String orderId,
String reason
) implements OrderCommand {}
// Command Handler
public class OrderCommandHandler {
private final OrderWriteRepository writeRepo;
private final EventPublisher eventPublisher;
public OrderCommandHandler(OrderWriteRepository writeRepo,
EventPublisher eventPublisher) {
this.writeRepo = writeRepo;
this.eventPublisher = eventPublisher;
}
public String handle(CreateOrderCommand cmd) {
Order order = Order.create(cmd.customerId(), cmd.items(), cmd.shippingAddress());
writeRepo.save(order);
eventPublisher.publish(new OrderCreatedEvent(order.getId(), order.getTotal()));
return order.getId();
}
public void handle(CancelOrderCommand cmd) {
Order order = writeRepo.findById(cmd.orderId())
.orElseThrow(() -> new OrderNotFoundException(cmd.orderId()));
order.cancel(cmd.reason());
writeRepo.save(order);
eventPublisher.publish(new OrderCancelledEvent(cmd.orderId(), cmd.reason()));
}
}
// Query側
public record OrderSummaryView(
String orderId,
String customerName,
BigDecimal total,
String status,
LocalDateTime createdAt
) {}
public interface OrderQueryService {
Optional<OrderSummaryView> getOrderSummary(String orderId);
List<OrderSummaryView> getOrdersByCustomer(String customerId);
List<OrderSummaryView> getRecentOrders(int limit);
OrderStatistics getStatistics(LocalDate from, LocalDate to);
}
// Query実装 — 読み取り専用のビューモデルに最適化
public class OrderQueryServiceImpl implements OrderQueryService {
private final OrderReadRepository readRepo;
public OrderQueryServiceImpl(OrderReadRepository readRepo) {
this.readRepo = readRepo;
}
@Override
public Optional<OrderSummaryView> getOrderSummary(String orderId) {
return readRepo.findSummaryById(orderId);
}
@Override
public List<OrderSummaryView> getRecentOrders(int limit) {
return readRepo.findRecent(limit);
}
@Override
public OrderStatistics getStatistics(LocalDate from, LocalDate to) {
return readRepo.calculateStatistics(from, to);
}
}
5.4 Event Sourcing(イベントソーシング)
目的
システムの状態変更をすべてイベントとして記録し、イベントの再生によって任意の時点の状態を復元できるようにする。
Python実装
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any
from uuid import uuid4
@dataclass(frozen=True)
class DomainEvent:
event_id: str = field(default_factory=lambda: str(uuid4()))
timestamp: datetime = field(default_factory=datetime.utcnow)
@dataclass(frozen=True)
class AccountCreated(DomainEvent):
account_id: str = ""
owner_name: str = ""
initial_balance: float = 0.0
@dataclass(frozen=True)
class MoneyDeposited(DomainEvent):
account_id: str = ""
amount: float = 0.0
description: str = ""
@dataclass(frozen=True)
class MoneyWithdrawn(DomainEvent):
account_id: str = ""
amount: float = 0.0
description: str = ""
# Event Sourced Aggregate
class BankAccount:
def __init__(self):
self.account_id: str = ""
self.owner_name: str = ""
self.balance: float = 0.0
self._uncommitted_events: list[DomainEvent] = []
self._version: int = 0
# Command Methods(イベントを生成)
@classmethod
def create(cls, account_id: str, owner_name: str, initial_balance: float) -> "BankAccount":
account = cls()
event = AccountCreated(
account_id=account_id,
owner_name=owner_name,
initial_balance=initial_balance,
)
account._apply(event)
account._uncommitted_events.append(event)
return account
def deposit(self, amount: float, description: str = "") -> None:
if amount <= 0:
raise ValueError("入金額は正の数でなければなりません")
event = MoneyDeposited(
account_id=self.account_id, amount=amount, description=description
)
self._apply(event)
self._uncommitted_events.append(event)
def withdraw(self, amount: float, description: str = "") -> None:
if amount <= 0:
raise ValueError("出金額は正の数でなければなりません")
if amount > self.balance:
raise ValueError(f"残高不足: 残高={self.balance}, 出金額={amount}")
event = MoneyWithdrawn(
account_id=self.account_id, amount=amount, description=description
)
self._apply(event)
self._uncommitted_events.append(event)
# イベントの適用(状態遷移)
def _apply(self, event: DomainEvent) -> None:
match event:
case AccountCreated():
self.account_id = event.account_id
self.owner_name = event.owner_name
self.balance = event.initial_balance
case MoneyDeposited():
self.balance += event.amount
case MoneyWithdrawn():
self.balance -= event.amount
self._version += 1
# イベント履歴からの再構築
@classmethod
def from_events(cls, events: list[DomainEvent]) -> "BankAccount":
account = cls()
for event in events:
account._apply(event)
return account
def get_uncommitted_events(self) -> list[DomainEvent]:
events = self._uncommitted_events.copy()
self._uncommitted_events.clear()
return events
# Event Store
class EventStore:
def __init__(self):
self._store: dict[str, list[DomainEvent]] = {}
def save(self, aggregate_id: str, events: list[DomainEvent]) -> None:
if aggregate_id not in self._store:
self._store[aggregate_id] = []
self._store[aggregate_id].extend(events)
def load(self, aggregate_id: str) -> list[DomainEvent]:
return self._store.get(aggregate_id, [])
def load_until(self, aggregate_id: str, until: datetime) -> list[DomainEvent]:
"""特定時点までのイベントを取得"""
return [
e for e in self._store.get(aggregate_id, [])
if e.timestamp <= until
]
# 使用例
store = EventStore()
# 口座作成と取引
account = BankAccount.create("ACC-001", "田中太郎", 10000)
account.deposit(5000, "給与振込")
account.withdraw(3000, "家賃")
account.deposit(2000, "副業収入")
# イベントの保存
store.save("ACC-001", account.get_uncommitted_events())
# イベント履歴から状態を復元
events = store.load("ACC-001")
restored = BankAccount.from_events(events)
print(f"口座: {restored.owner_name}, 残高: ¥{restored.balance:,.0f}")
# 口座: 田中太郎, 残高: ¥14,000
5.5 Circuit Breaker(サーキットブレーカー)
目的
外部サービスの障害がシステム全体に波及することを防ぐ。障害が検出されると「回路を開き」、一定時間後に回復を試みる。
状態遷移
成功 失敗回数が閾値超過
┌──────────┐ ┌──────────────────┐
│ │ │ │
│ CLOSED │────────>│ OPEN │
│ (正常稼働) │ │ (即座にエラー返却) │
│ │<──┐ │ │
└──────────┘ │ └────────┬─────────┘
│ │ タイムアウト経過
│ ┌────────▼─────────┐
│ │ HALF-OPEN │
└─────│ (試行的にリクエスト)│
成功 └──────────────────┘
│ 失敗 → OPENに戻る
TypeScript実装
enum CircuitState {
CLOSED = "CLOSED",
OPEN = "OPEN",
HALF_OPEN = "HALF_OPEN",
}
interface CircuitBreakerOptions {
failureThreshold: number; // OPENにする失敗回数
resetTimeoutMs: number; // HALF_OPENに移行するまでの時間
halfOpenMaxAttempts: number; // HALF_OPENでの試行回数
}
class CircuitBreaker {
private state = CircuitState.CLOSED;
private failureCount = 0;
private lastFailureTime = 0;
private halfOpenAttempts = 0;
constructor(
private readonly name: string,
private readonly options: CircuitBreakerOptions = {
failureThreshold: 5,
resetTimeoutMs: 30_000,
halfOpenMaxAttempts: 3,
}
) {}
async execute<T>(action: () => Promise<T>): Promise<T> {
if (this.state === CircuitState.OPEN) {
if (Date.now() - this.lastFailureTime >= this.options.resetTimeoutMs) {
this.transitionTo(CircuitState.HALF_OPEN);
} else {
throw new Error(
`Circuit breaker "${this.name}" is OPEN. Retry after ${this.getRemainingTimeout()}ms`
);
}
}
try {
const result = await action();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
private onSuccess(): void {
if (this.state === CircuitState.HALF_OPEN) {
this.halfOpenAttempts++;
if (this.halfOpenAttempts >= this.options.halfOpenMaxAttempts) {
this.transitionTo(CircuitState.CLOSED);
}
}
this.failureCount = 0;
}
private onFailure(): void {
this.failureCount++;
this.lastFailureTime = Date.now();
if (this.state === CircuitState.HALF_OPEN) {
this.transitionTo(CircuitState.OPEN);
} else if (this.failureCount >= this.options.failureThreshold) {
this.transitionTo(CircuitState.OPEN);
}
}
private transitionTo(newState: CircuitState): void {
console.log(`[CircuitBreaker:${this.name}] ${this.state} -> ${newState}`);
this.state = newState;
if (newState === CircuitState.CLOSED) {
this.failureCount = 0;
this.halfOpenAttempts = 0;
} else if (newState === CircuitState.HALF_OPEN) {
this.halfOpenAttempts = 0;
}
}
private getRemainingTimeout(): number {
return Math.max(
0,
this.options.resetTimeoutMs - (Date.now() - this.lastFailureTime)
);
}
getState(): CircuitState { return this.state; }
}
// 使用例
const paymentCircuit = new CircuitBreaker("payment-api", {
failureThreshold: 3,
resetTimeoutMs: 10_000,
halfOpenMaxAttempts: 2,
});
async function processPayment(amount: number): Promise<string> {
return paymentCircuit.execute(async () => {
const response = await fetch("https://api.payment.com/charge", {
method: "POST",
body: JSON.stringify({ amount }),
});
if (!response.ok) throw new Error("Payment failed");
return (await response.json()).transactionId;
});
}
5.6 Saga(サーガ)
目的
分散トランザクションを複数のローカルトランザクションに分割し、各ステップに補償トランザクション(ロールバック操作)を定義する。
Python実装
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Callable
class SagaStepStatus(Enum):
PENDING = "PENDING"
COMPLETED = "COMPLETED"
COMPENSATED = "COMPENSATED"
FAILED = "FAILED"
@dataclass
class SagaStep:
name: str
action: Callable[..., Any]
compensation: Callable[..., Any]
status: SagaStepStatus = SagaStepStatus.PENDING
result: Any = None
class SagaOrchestrator:
"""Orchestration-based Saga"""
def __init__(self, name: str):
self.name = name
self._steps: list[SagaStep] = []
self._context: dict[str, Any] = {}
def add_step(
self,
name: str,
action: Callable,
compensation: Callable,
) -> "SagaOrchestrator":
self._steps.append(SagaStep(name=name, action=action, compensation=compensation))
return self
def execute(self, initial_context: dict[str, Any] | None = None) -> dict[str, Any]:
self._context = initial_context or {}
completed_steps: list[SagaStep] = []
print(f"=== Saga '{self.name}' started ===")
for step in self._steps:
try:
print(f" Executing: {step.name}")
step.result = step.action(self._context)
step.status = SagaStepStatus.COMPLETED
completed_steps.append(step)
if isinstance(step.result, dict):
self._context.update(step.result)
except Exception as e:
print(f" FAILED: {step.name} - {e}")
step.status = SagaStepStatus.FAILED
self._compensate(completed_steps)
raise SagaException(
f"Saga '{self.name}' failed at step '{step.name}': {e}"
) from e
print(f"=== Saga '{self.name}' completed successfully ===")
return self._context
def _compensate(self, completed_steps: list[SagaStep]) -> None:
print(" --- Starting compensation ---")
for step in reversed(completed_steps):
try:
print(f" Compensating: {step.name}")
step.compensation(self._context)
step.status = SagaStepStatus.COMPENSATED
except Exception as e:
print(f" COMPENSATION FAILED: {step.name} - {e}")
# 補償失敗はログに記録し、手動介入が必要
class SagaException(Exception):
pass
# 使用例 — 旅行予約Saga
def reserve_flight(ctx: dict) -> dict:
print(" -> Flight reserved: Tokyo -> New York")
return {"flight_id": "FL-001"}
def cancel_flight(ctx: dict) -> None:
print(f" -> Flight {ctx.get('flight_id')} cancelled")
def reserve_hotel(ctx: dict) -> dict:
print(" -> Hotel reserved: Manhattan Hotel")
return {"hotel_id": "HT-001"}
def cancel_hotel(ctx: dict) -> None:
print(f" -> Hotel {ctx.get('hotel_id')} cancelled")
def charge_payment(ctx: dict) -> dict:
print(" -> Payment charged: $2,500")
# raise Exception("Payment declined") # テスト用
return {"payment_id": "PAY-001"}
def refund_payment(ctx: dict) -> None:
print(f" -> Payment {ctx.get('payment_id')} refunded")
# Saga構築・実行
saga = (
SagaOrchestrator("travel-booking")
.add_step("Reserve Flight", reserve_flight, cancel_flight)
.add_step("Reserve Hotel", reserve_hotel, cancel_hotel)
.add_step("Charge Payment", charge_payment, refund_payment)
)
try:
result = saga.execute({"customer_id": "CUST-001", "trip": "Tokyo-NYC"})
print(f"Booking complete: {result}")
except SagaException as e:
print(f"Booking failed: {e}")
5.7 Sidecar(サイドカー)
目的
アプリケーションの補助的な機能(ログ収集、サービスメッシュプロキシ、設定管理など)を、アプリケーション本体と同じホスト上の別プロセスとして実行する。
説明とKubernetes設定例
Sidecarパターンは、Kubernetesのようなコンテナオーケストレーション環境で広く使われている。
# Kubernetes Pod with Sidecar — ログ収集サイドカー
apiVersion: v1
kind: Pod
metadata:
name: app-with-log-sidecar
labels:
app: my-application
spec:
containers:
# メインアプリケーション
- name: app
image: my-app:latest
ports:
- containerPort: 8080
volumeMounts:
- name: log-volume
mountPath: /var/log/app
# Sidecar — ログ収集
- name: log-collector
image: fluent-bit:latest
volumeMounts:
- name: log-volume
mountPath: /var/log/app
readOnly: true
- name: fluent-config
mountPath: /fluent-bit/etc/
resources:
limits:
cpu: 100m
memory: 128Mi
# Sidecar — Envoy Proxy(サービスメッシュ)
- name: envoy-proxy
image: envoyproxy/envoy:v1.28
ports:
- containerPort: 9901 # Admin
- containerPort: 15001 # Outbound
volumeMounts:
- name: envoy-config
mountPath: /etc/envoy
volumes:
- name: log-volume
emptyDir: {}
- name: fluent-config
configMap:
name: fluent-bit-config
- name: envoy-config
configMap:
name: envoy-config
# Istio Service Mesh Sidecar設定
apiVersion: networking.istio.io/v1beta1
kind: Sidecar
metadata:
name: default
namespace: my-namespace
spec:
egress:
- hosts:
- "./*" # 同じ名前空間のすべてのサービス
- "istio-system/*" # istio-systemのサービス
outboundTrafficPolicy:
mode: REGISTRY_ONLY # 登録済みサービスのみ許可
5.8 モダンパターンのまとめ
| パターン | 目的 | 主な適用場面 |
|---|---|---|
| Dependency Injection | 依存関係の外部注入 | テスト容易性、疎結合設計 |
| Repository | データアクセスの抽象化 | ドメイン駆動設計、永続化層 |
| CQRS | 読み書きの責務分離 | 高トラフィック、非対称な読み書き |
| Event Sourcing | 状態変更のイベント記録 | 監査証跡、時系列データ |
| Circuit Breaker | 障害の波及防止 | マイクロサービス間通信 |
| Saga | 分散トランザクション管理 | マイクロサービスの整合性確保 |
| Sidecar | 補助機能の分離 | サービスメッシュ、ログ収集 |
6. アンチパターン
デザインパターンが「良い設計の定石」であるのに対し、アンチパターン(Anti-patterns)は「一見良さそうに見えるが、実際には悪影響をもたらす設計や実装の習慣」である。アンチパターンを認識することは、良い設計を学ぶのと同様に重要である。
6.1 God Object(神オブジェクト)
説明
一つのクラスがあまりにも多くの責務を持ち、システム全体の情報や処理を集中管理している状態。「何でも知っている、何でもできる」オブジェクトである。
問題のあるコード例
// アンチパターン: God Object
public class ApplicationManager {
private Database db;
private HttpClient httpClient;
private EmailSender emailSender;
private FileSystem fileSystem;
private Logger logger;
private Cache cache;
// ユーザー管理
public User createUser(String name, String email) { /* ... */ }
public User findUser(String id) { /* ... */ }
public void updateUser(User user) { /* ... */ }
public void deleteUser(String id) { /* ... */ }
// 注文管理
public Order createOrder(String userId, List<Item> items) { /* ... */ }
public void processOrder(String orderId) { /* ... */ }
public void cancelOrder(String orderId) { /* ... */ }
// 通知
public void sendEmail(String to, String subject, String body) { /* ... */ }
public void sendPushNotification(String userId, String msg) { /* ... */ }
// レポート
public byte[] generateSalesReport(Date from, Date to) { /* ... */ }
public byte[] generateUserReport() { /* ... */ }
// 認証
public String authenticate(String username, String password) { /* ... */ }
public boolean authorize(String userId, String resource) { /* ... */ }
// その他100以上のメソッド...
}
リファクタリング後
// 責務ごとにクラスを分離
public class UserService {
private final UserRepository userRepo;
private final PasswordEncoder encoder;
public UserService(UserRepository userRepo, PasswordEncoder encoder) {
this.userRepo = userRepo;
this.encoder = encoder;
}
public User createUser(String name, String email) { /* ... */ }
public Optional<User> findUser(String id) { /* ... */ }
}
public class OrderService {
private final OrderRepository orderRepo;
private final PaymentService paymentService;
private final NotificationService notificationService;
public OrderService(OrderRepository orderRepo,
PaymentService paymentService,
NotificationService notificationService) {
this.orderRepo = orderRepo;
this.paymentService = paymentService;
this.notificationService = notificationService;
}
public Order createOrder(String userId, List<Item> items) { /* ... */ }
public void processOrder(String orderId) { /* ... */ }
}
public class AuthService {
private final UserRepository userRepo;
private final TokenService tokenService;
public String authenticate(String username, String password) { /* ... */ }
public boolean authorize(String userId, String resource) { /* ... */ }
}
6.2 Spaghetti Code(スパゲッティコード)
説明
構造がなく、制御フローが複雑に絡み合ったコード。goto文の多用や、深いネストの条件分岐が特徴。
問題のあるコード例
# アンチパターン: Spaghetti Code
def process_order(order_data):
if order_data:
if order_data.get("items"):
total = 0
for item in order_data["items"]:
if item.get("price") and item.get("quantity"):
if item["quantity"] > 0:
subtotal = item["price"] * item["quantity"]
if order_data.get("discount"):
if order_data["discount"]["type"] == "percentage":
subtotal = subtotal * (1 - order_data["discount"]["value"] / 100)
elif order_data["discount"]["type"] == "fixed":
subtotal = subtotal - order_data["discount"]["value"]
if subtotal < 0:
subtotal = 0
total += subtotal
if item.get("taxable"):
if order_data.get("tax_rate"):
total += subtotal * order_data["tax_rate"]
else:
return {"error": "Invalid quantity"}
else:
return {"error": "Missing price or quantity"}
if total > 0:
if order_data.get("payment_method"):
# 決済処理...
pass
else:
return {"error": "No payment method"}
else:
return {"error": "Empty order"}
else:
return {"error": "No items"}
else:
return {"error": "No order data"}
リファクタリング後
# Early Return + 関数分割で改善
from dataclasses import dataclass
from typing import Optional
@dataclass
class OrderItem:
price: float
quantity: int
taxable: bool = False
@dataclass
class Discount:
type: str # "percentage" or "fixed"
value: float
@dataclass
class OrderData:
items: list[OrderItem]
discount: Optional[Discount] = None
tax_rate: float = 0.0
payment_method: Optional[str] = None
class OrderError(Exception):
pass
def calculate_item_subtotal(item: OrderItem, discount: Optional[Discount]) -> float:
if item.quantity <= 0:
raise OrderError("Invalid quantity")
subtotal = item.price * item.quantity
if discount:
subtotal = apply_discount(subtotal, discount)
return subtotal
def apply_discount(amount: float, discount: Discount) -> float:
if discount.type == "percentage":
return amount * (1 - discount.value / 100)
elif discount.type == "fixed":
return max(0, amount - discount.value)
return amount
def calculate_tax(subtotal: float, item: OrderItem, tax_rate: float) -> float:
if item.taxable and tax_rate > 0:
return subtotal * tax_rate
return 0.0
def process_order(order: OrderData) -> dict:
if not order.items:
raise OrderError("No items")
if not order.payment_method:
raise OrderError("No payment method")
total = 0.0
for item in order.items:
subtotal = calculate_item_subtotal(item, order.discount)
tax = calculate_tax(subtotal, item, order.tax_rate)
total += subtotal + tax
if total <= 0:
raise OrderError("Empty order")
return {"total": total, "status": "success"}
6.3 Golden Hammer(金のハンマー)
説明
「ハンマーを持つ者にはすべてが釘に見える」— ある特定の技術やパターンを、あらゆる問題に適用しようとするアンチパターン。
例
// アンチパターン: すべてにSingletonを使う
class UserSingleton {
private static instance: UserSingleton;
private name: string = "";
private email: string = "";
static getInstance(): UserSingleton {
if (!this.instance) this.instance = new UserSingleton();
return this.instance;
}
// 1人のユーザーしか扱えない...
}
// アンチパターン: すべてにMicroservicesを使う
// 単純なTodoアプリにまで:
// - todo-service
// - user-service
// - notification-service
// - api-gateway
// - service-registry
// → オーバーエンジニアリング
// 正しいアプローチ: 問題に合った解決策を選ぶ
// 小規模: モノリシックアーキテクチャで十分
// 中規模: モジュラーモノリス
// 大規模: マイクロサービスを検討
回避方法
- 技術選定時に複数の選択肢を比較する
- 「この問題にこの技術が本当に適切か?」を常に問う
- プロトタイプで検証する
- チーム内でレビューし、偏りを指摘し合う
6.4 Lava Flow(溶岩流)
説明
不要になったコード(デッドコード)がシステム内に残り続け、誰も触れない「溶岩」のように固まっている状態。「何のためのコードか分からないが、消すと何かが壊れるかもしれない」という恐怖からそのまま残される。
回避方法
# 1. テストカバレッジを十分に確保する
# テストがあれば、コード削除時に影響を検知できる
# 2. フィーチャーフラグの適切な管理
class FeatureFlags:
"""期限切れのフラグを検出する仕組み"""
_flags: dict[str, dict] = {}
@classmethod
def register(cls, name: str, default: bool, expiry_date: str):
cls._flags[name] = {
"default": default,
"expiry": expiry_date,
}
@classmethod
def audit_expired(cls) -> list[str]:
"""期限切れフラグを検出"""
from datetime import date
today = date.today().isoformat()
return [
name for name, config in cls._flags.items()
if config["expiry"] < today
]
# 3. 定期的なコードベースの棚卸し
# - 使用されていないimport文を削除
# - コメントアウトされたコードを削除
# - 到達不能なコードを削除
# - 未使用のメソッド・クラスを削除
6.5 Premature Optimization(早すぎる最適化)
説明
Donald Knuthの有名な言葉「早すぎる最適化は諸悪の根源」に基づくアンチパターン。計測もせずにパフォーマンスを推測し、可読性や保守性を犠牲にして最適化してしまう。
// アンチパターン: 読みにくい「最適化」コード
public int sumArray(int[] arr) {
int sum = 0;
// ループアンローリング(コンパイラが自動でやってくれる)
int i = 0;
int len = arr.length;
int rem = len % 4;
for (; i < rem; i++) sum += arr[i];
for (; i < len; i += 4) {
sum += arr[i] + arr[i+1] + arr[i+2] + arr[i+3];
}
return sum;
}
// 正しいアプローチ: まず明快に書き、計測してからボトルネックのみ最適化
public int sumArray(int[] arr) {
return Arrays.stream(arr).sum();
}
6.6 Cargo Cult Programming(カーゴ・カルト・プログラミング)
説明
他のプロジェクトやサンプルコードからコードをコピーし、なぜそう書かれているかを理解せずに使うパターン。「あのプロジェクトで使っていたから」という理由だけで採用する。
// アンチパターン: 理由なく複雑なパターンを導入
// 単純なCRUDアプリに不必要なAbstract Factoryパターン
// 実際に必要なもの
class UserController {
constructor(private userService: UserService) {}
async getUser(id: string): Promise<User> {
return this.userService.findById(id);
}
}
// 不必要な複雑さ
interface ControllerFactory {
createController(): Controller;
}
interface ServiceFactory {
createService(): Service;
}
interface RepositoryFactory {
createRepository(): Repository;
}
// ...ファクトリーのファクトリーが延々と続く
回避方法
- コードを書く前に「なぜこの設計にするのか」を説明できるようにする
- YAGNI(You Aren't Gonna Need It)原則を守る
- 既存コードを流用する際は、そのコードの意図を理解する
6.7 アンチパターンのまとめ
| アンチパターン | 症状 | 回避策 |
|---|---|---|
| God Object | 巨大クラス、多すぎる責務 | SRP、クラスの分割 |
| Spaghetti Code | 深いネスト、複雑な制御フロー | Early Return、関数分割 |
| Golden Hammer | 一つの技術に固執 | 複数の選択肢を比較検討 |
| Lava Flow | デッドコードの放置 | テスト、定期的な棚卸し |
| Premature Optimization | 計測前の最適化 | 計測してからボトルネックを最適化 |
| Cargo Cult | 理由なきコピー | コードの意図を理解する |
7. 実践ガイド
7.1 パターンの選び方
デザインパターンの適用は「パターンありき」ではなく「問題ありき」で行うべきである。以下の判断フローを参考にされたい。
問題別パターン選択ガイド
| 解決したい問題 | 候補パターン | 選択の判断基準 |
|---|---|---|
| オブジェクト生成を柔軟にしたい | Factory Method, Abstract Factory | 生成するクラスが1種類ならFactory Method、ファミリーならAbstract Factory |
| 多数のパラメータを持つオブジェクトを作りたい | Builder | コンストラクタの引数が4つ以上、オプショナルなパラメータが多い場合 |
| アルゴリズムを交換可能にしたい | Strategy | 実行時にアルゴリズムを切り替える必要がある場合 |
| アルゴリズムの骨格を定義し一部をカスタマイズしたい | Template Method | 処理の流れは共通で一部のステップだけ異なる場合 |
| オブジェクトの状態に応じて振る舞いを変えたい | State | if-elseやswitchの分岐が多い場合 |
| イベントを複数の受信者に通知したい | Observer | Pub/Sub、イベント駆動アーキテクチャ |
| 複雑なサブシステムをシンプルに使いたい | Facade | APIの簡素化、レガシーシステムのラッピング |
| 既存クラスに機能を動的に追加したい | Decorator | 継承の爆発を避けたい場合 |
| 異なるインターフェースを統一したい | Adapter | サードパーティ統合、レガシー統合 |
| 分散サービスの障害に対応したい | Circuit Breaker, Saga | サービス間通信の信頼性確保 |
パターン適用の判断基準
1. 問題の特定
└─ 「何が問題なのか」を明確に言語化する
2. パターンの候補を挙げる
└─ 上記の表や知識ベースから該当パターンを検索
3. トレードオフの評価
├─ 複雑さの増加 vs 得られる柔軟性
├─ パフォーマンスへの影響
└─ チームの理解度
4. YAGNI原則の適用
└─ 「今本当に必要か?」を問う
将来の変更に備えて過度に抽象化しない
5. 段階的な適用
└─ 最初はシンプルに実装し、
必要に応じてリファクタリングでパターンを導入
7.2 パターンの組み合わせ
実際のシステムでは、複数のパターンを組み合わせて使うことが多い。以下に代表的な組み合わせを示す。
Factory Method + Strategy
// Strategyを生成するFactory
public interface CompressionStrategy {
byte[] compress(byte[] data);
byte[] decompress(byte[] data);
}
public class GzipCompression implements CompressionStrategy {
@Override
public byte[] compress(byte[] data) { /* gzip圧縮 */ return data; }
@Override
public byte[] decompress(byte[] data) { /* gzip解凍 */ return data; }
}
public class ZstdCompression implements CompressionStrategy {
@Override
public byte[] compress(byte[] data) { /* zstd圧縮 */ return data; }
@Override
public byte[] decompress(byte[] data) { /* zstd解凍 */ return data; }
}
// Factory MethodでStrategyを生成
public class CompressionFactory {
public static CompressionStrategy create(String algorithm) {
return switch (algorithm.toLowerCase()) {
case "gzip" -> new GzipCompression();
case "zstd" -> new ZstdCompression();
default -> throw new IllegalArgumentException(
"Unknown algorithm: " + algorithm);
};
}
}
// 使用
CompressionStrategy strategy = CompressionFactory.create(
config.get("compression.algorithm", "gzip"));
byte[] compressed = strategy.compress(data);
Observer + Command
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime
from typing import Callable
# CommandをEventとして扱い、Observerに通知する
@dataclass
class AuditEvent:
action: str
user_id: str
resource: str
timestamp: datetime = field(default_factory=datetime.utcnow)
details: dict = field(default_factory=dict)
class AuditObserver(ABC):
@abstractmethod
def on_event(self, event: AuditEvent) -> None:
pass
class ConsoleAuditLogger(AuditObserver):
def on_event(self, event: AuditEvent) -> None:
print(f"[AUDIT] {event.timestamp} | {event.user_id} | "
f"{event.action} | {event.resource}")
class DatabaseAuditLogger(AuditObserver):
def on_event(self, event: AuditEvent) -> None:
# DBに保存
print(f"[DB] Saving audit event: {event.action}")
class AuditedCommand(ABC):
"""Command + Observer: コマンド実行時に自動で監査ログを記録"""
_observers: list[AuditObserver] = []
@classmethod
def add_observer(cls, observer: AuditObserver):
cls._observers.append(observer)
@abstractmethod
def execute(self) -> None:
pass
@abstractmethod
def get_audit_event(self) -> AuditEvent:
pass
def execute_with_audit(self) -> None:
self.execute()
event = self.get_audit_event()
for observer in self._observers:
observer.on_event(event)
class DeleteUserCommand(AuditedCommand):
def __init__(self, user_id: str, performed_by: str):
self.user_id = user_id
self.performed_by = performed_by
def execute(self) -> None:
print(f"Deleting user {self.user_id}")
def get_audit_event(self) -> AuditEvent:
return AuditEvent(
action="DELETE",
user_id=self.performed_by,
resource=f"user/{self.user_id}",
)
# セットアップ
AuditedCommand.add_observer(ConsoleAuditLogger())
AuditedCommand.add_observer(DatabaseAuditLogger())
# 実行
cmd = DeleteUserCommand("USR-001", "ADMIN-001")
cmd.execute_with_audit()
Decorator + Factory
// ミドルウェアパイプラインをDecoratorとFactoryで構築
interface HttpHandler {
handle(request: Request): Promise<Response>;
}
type MiddlewareFactory = (next: HttpHandler) => HttpHandler;
// Decorator として機能するミドルウェア
function loggingMiddleware(): MiddlewareFactory {
return (next: HttpHandler): HttpHandler => ({
async handle(request: Request): Promise<Response> {
const start = Date.now();
console.log(`--> ${request.method} ${request.url}`);
const response = await next.handle(request);
console.log(`<-- ${request.method} ${request.url} ${Date.now() - start}ms`);
return response;
},
});
}
function authMiddleware(secret: string): MiddlewareFactory {
return (next: HttpHandler): HttpHandler => ({
async handle(request: Request): Promise<Response> {
const token = request.headers.get("Authorization");
if (!token) {
return new Response("Unauthorized", { status: 401 });
}
return next.handle(request);
},
});
}
function corsMiddleware(origins: string[]): MiddlewareFactory {
return (next: HttpHandler): HttpHandler => ({
async handle(request: Request): Promise<Response> {
const response = await next.handle(request);
response.headers.set("Access-Control-Allow-Origin", origins.join(","));
return response;
},
});
}
// Factory でパイプラインを構築
function createPipeline(
handler: HttpHandler,
...middlewares: MiddlewareFactory[]
): HttpHandler {
return middlewares.reduceRight(
(next, middleware) => middleware(next),
handler
);
}
// 使用例
const app = createPipeline(
{ handle: async (req) => new Response("OK") },
loggingMiddleware(),
corsMiddleware(["https://example.com"]),
authMiddleware("secret-key")
);
7.3 リファクタリングでのパターン適用
リファクタリングの際にデザインパターンを段階的に導入する方法を示す。
Before: 条件分岐の多いコード
class PaymentProcessor:
def process(self, payment_type: str, amount: float, details: dict) -> dict:
if payment_type == "credit_card":
card = details["card_number"]
expiry = details["expiry"]
cvv = details["cvv"]
# カード検証
if not self._validate_card(card):
return {"success": False, "error": "Invalid card"}
# 3Dセキュア
if amount > 10000:
if not self._verify_3ds(card):
return {"success": False, "error": "3DS failed"}
# 決済
result = self._charge_card(card, amount)
return {"success": True, "transaction_id": result}
elif payment_type == "bank_transfer":
bank_code = details["bank_code"]
account = details["account_number"]
# 口座検証
if not self._validate_account(bank_code, account):
return {"success": False, "error": "Invalid account"}
result = self._transfer(bank_code, account, amount)
return {"success": True, "transaction_id": result}
elif payment_type == "e_wallet":
wallet_id = details["wallet_id"]
# 残高確認
balance = self._check_wallet_balance(wallet_id)
if balance < amount:
return {"success": False, "error": "Insufficient balance"}
result = self._debit_wallet(wallet_id, amount)
return {"success": True, "transaction_id": result}
else:
return {"success": False, "error": "Unknown payment type"}
After: Strategyパターンの適用
from abc import ABC, abstractmethod
from dataclasses import dataclass
@dataclass
class PaymentResult:
success: bool
transaction_id: str = ""
error: str = ""
class PaymentStrategy(ABC):
@abstractmethod
def validate(self, amount: float, details: dict) -> str | None:
"""検証。エラーがあればメッセージを返す"""
pass
@abstractmethod
def charge(self, amount: float, details: dict) -> str:
"""決済実行。transaction_idを返す"""
pass
def process(self, amount: float, details: dict) -> PaymentResult:
error = self.validate(amount, details)
if error:
return PaymentResult(success=False, error=error)
txn_id = self.charge(amount, details)
return PaymentResult(success=True, transaction_id=txn_id)
class CreditCardPayment(PaymentStrategy):
def validate(self, amount: float, details: dict) -> str | None:
if not self._validate_card(details["card_number"]):
return "Invalid card"
if amount > 10000 and not self._verify_3ds(details["card_number"]):
return "3DS verification failed"
return None
def charge(self, amount: float, details: dict) -> str:
return self._charge_card(details["card_number"], amount)
class BankTransferPayment(PaymentStrategy):
def validate(self, amount: float, details: dict) -> str | None:
if not self._validate_account(details["bank_code"], details["account_number"]):
return "Invalid account"
return None
def charge(self, amount: float, details: dict) -> str:
return self._transfer(details["bank_code"], details["account_number"], amount)
class EWalletPayment(PaymentStrategy):
def validate(self, amount: float, details: dict) -> str | None:
balance = self._check_balance(details["wallet_id"])
if balance < amount:
return "Insufficient balance"
return None
def charge(self, amount: float, details: dict) -> str:
return self._debit(details["wallet_id"], amount)
# Factory + Strategy
class PaymentProcessorV2:
_strategies: dict[str, type[PaymentStrategy]] = {
"credit_card": CreditCardPayment,
"bank_transfer": BankTransferPayment,
"e_wallet": EWalletPayment,
}
@classmethod
def register(cls, payment_type: str, strategy_class: type[PaymentStrategy]):
cls._strategies[payment_type] = strategy_class
def process(self, payment_type: str, amount: float, details: dict) -> PaymentResult:
strategy_class = self._strategies.get(payment_type)
if not strategy_class:
return PaymentResult(success=False, error=f"Unknown type: {payment_type}")
strategy = strategy_class()
return strategy.process(amount, details)
7.4 フレームワークにおけるパターンの使用例
実際の有名なフレームワークやライブラリで、どのようにデザインパターンが使われているかを知ることは、パターンの実践的な理解に役立つ。
| フレームワーク/ライブラリ | パターン | 使用箇所 |
|---|---|---|
| Spring Framework | DI, Factory, Proxy, Template Method | IoC Container, JdbcTemplate, AOP |
| React | Composite, Observer, Strategy | コンポーネントツリー、State/Props、Hooks |
| Express.js / Koa | Chain of Responsibility, Decorator | ミドルウェアパイプライン |
| Java I/O | Decorator | BufferedReader, InputStreamReader |
| Java Collections | Iterator, Factory Method | iterator(), List.of() |
| Django | Template Method, Observer | CBV, Signals |
| Redux | Observer, Command | Store Subscribe, Actions |
| Kubernetes | Sidecar, Observer | Pod構成, Controller Pattern |
| Hibernate/JPA | Proxy, Repository | Lazy Loading, EntityManager |
| gRPC | Builder, Strategy | RequestBuilder, LoadBalancer |
7.5 パターン適用時の注意点
- KISS原則を守る: パターンの適用で複雑さが増すなら、より単純な解決策を選ぶ
- YAGNI原則を守る: 「将来必要になるかもしれない」は適用理由にならない
- 段階的に適用する: 最初はシンプルに実装し、必要に応じてパターンを導入
- チームで合意する: パターンの適用はチームの理解度と合意が重要
- テストを先に書く: TDDでパターンの必要性を確認する
- 過度な抽象化を避ける: 抽象化の層が深すぎると追跡が困難になる
8. まとめ
8.1 デザインパターンの全体像
本記事では、GoFの23パターン、モダンパターン、アンチパターンを網羅的に解説した。以下にパターン全体の一覧を示す。
GoFパターン一覧
| # | カテゴリ | パターン名 | 一言での説明 |
|---|---|---|---|
| 1 | 生成 | Singleton | インスタンスを1つに制限 |
| 2 | 生成 | Factory Method | 生成をサブクラスに委譲 |
| 3 | 生成 | Abstract Factory | 関連オブジェクト群の生成 |
| 4 | 生成 | Builder | 複雑なオブジェクトの段階的構築 |
| 5 | 生成 | Prototype | 既存オブジェクトの複製 |
| 6 | 構造 | Adapter | 互換性のないインターフェースの接続 |
| 7 | 構造 | Bridge | 抽象と実装の分離 |
| 8 | 構造 | Composite | ツリー構造の統一的な扱い |
| 9 | 構造 | Decorator | 動的な機能追加 |
| 10 | 構造 | Facade | 複雑なサブシステムの簡素化 |
| 11 | 構造 | Flyweight | メモリ効率的なオブジェクト共有 |
| 12 | 構造 | Proxy | アクセス制御・付加機能 |
| 13 | 振る舞い | Chain of Responsibility | リクエストをチェーンで処理 |
| 14 | 振る舞い | Command | リクエストのオブジェクト化 |
| 15 | 振る舞い | Iterator | コレクションの順次アクセス |
| 16 | 振る舞い | Mediator | オブジェクト間通信の集約 |
| 17 | 振る舞い | Memento | 状態の保存と復元 |
| 18 | 振る舞い | Observer | 状態変化の通知 |
| 19 | 振る舞い | State | 状態に応じた振る舞いの変更 |
| 20 | 振る舞い | Strategy | アルゴリズムの交換可能性 |
| 21 | 振る舞い | Template Method | アルゴリズムの骨格定義 |
| 22 | 振る舞い | Visitor | 構造を変えずに操作を追加 |
※ GoFの23パターンのうち、Interpreterパターンは本記事では詳細を省略した。
8.2 パターン選択のクイックリファレンス
「オブジェクトの作り方」に関する問題
├─ インスタンスを1つに制限したい → Singleton
├─ 生成するクラスを実行時に決めたい → Factory Method
├─ 関連するオブジェクト群を一括で作りたい → Abstract Factory
├─ パラメータが多い複雑なオブジェクトを作りたい → Builder
└─ 既存オブジェクトをコピーして作りたい → Prototype
「クラスの構造・組み合わせ」に関する問題
├─ 互換性のないインターフェースを統一したい → Adapter
├─ 抽象と実装を独立に変更したい → Bridge
├─ ツリー構造を統一的に扱いたい → Composite
├─ 機能を動的に追加したい → Decorator
├─ 複雑なサブシステムを簡単に使いたい → Facade
├─ メモリを節約したい → Flyweight
└─ アクセスを制御・拡張したい → Proxy
「オブジェクトの振る舞い」に関する問題
├─ リクエストを複数のハンドラで順に処理したい → Chain of Responsibility
├─ 操作をオブジェクトとして記録・取消したい → Command
├─ コレクションを順に走査したい → Iterator
├─ オブジェクト間の通信を整理したい → Mediator
├─ 状態を保存・復元したい → Memento
├─ 変化を通知・購読したい → Observer
├─ 状態に応じて振る舞いを変えたい → State
├─ アルゴリズムを差し替え可能にしたい → Strategy
├─ 処理の流れを固定し一部だけ変えたい → Template Method
└─ 構造を変えずに新しい処理を追加したい → Visitor
「分散システム・クラウド」に関する問題
├─ 依存関係を疎結合にしたい → Dependency Injection
├─ データアクセスを抽象化したい → Repository
├─ 読み書きを分離してスケールしたい → CQRS
├─ 全ての状態変更を追跡したい → Event Sourcing
├─ 外部サービス障害の連鎖を防ぎたい → Circuit Breaker
├─ 分散トランザクションを管理したい → Saga
└─ 補助機能をアプリ本体から分離したい → Sidecar
8.3 学習の次のステップ
- 実際のプロジェクトで適用する: 小さなプロジェクトから始め、パターンを1つずつ試す
- ソースコードリーディング: Spring, React, Djangoなどのフレームワークのソースを読み、パターンの実用例を学ぶ
- ドメイン駆動設計(DDD)を学ぶ: デザインパターンをドメインモデリングに応用する
- アーキテクチャパターンを学ぶ: マイクロサービス、ヘキサゴナルアーキテクチャ、Clean Architectureなど
- チームで勉強会を行う: パターンについて議論し、チームの共通語彙を育てる
8.4 参考文献
| 書籍/リソース | 著者 | 概要 |
|---|---|---|
| Design Patterns: Elements of Reusable Object-Oriented Software | Gamma, Helm, Johnson, Vlissides | GoFパターンの原典 |
| Head First Design Patterns | Freeman, Robson | 図解で分かりやすいパターン入門書 |
| Refactoring: Improving the Design of Existing Code | Martin Fowler | リファクタリングとパターンの関係 |
| Clean Code | Robert C. Martin | コードの品質向上とSOLID原則 |
| Clean Architecture | Robert C. Martin | アーキテクチャレベルでの設計原則 |
| Domain-Driven Design | Eric Evans | ドメイン駆動設計とパターンの統合 |
| Patterns of Enterprise Application Architecture | Martin Fowler | エンタープライズパターン |
| Building Microservices | Sam Newman | マイクロサービスのパターン |
| Release It! | Michael Nygard | Circuit Breakerなどの安定性パターン |
| Refactoring.Guru | — | デザインパターンの視覚的な解説サイト |
| Source Making | — | パターンとアンチパターンの解説サイト |
本記事は、ソフトウェア設計の基礎から実践まで、デザインパターンを体系的に理解するためのガイドとして作成された。パターンは万能薬ではないが、適切に適用することで、保守性、拡張性、テスト容易性に優れたソフトウェアを構築する強力な武器となる。
最終更新: 2026年4月