Skip to content
Published on

デザインパターン2025実践ガイド: GoF 23パターンをモダン言語(TypeScript/Python/Go)で再解釈

Authors

目次

1. 2025年にデザインパターンが重要な理由

1994年にGang of Four(GoF)が発表した23個のデザインパターンは、30年経った今でもソフトウェアエンジニアリングの共通言語です。しかし、世界は変わりました。

モダン開発環境の変化:

  • OOP中心から関数型(かんすうがた)+リアクティブのハイブリッドへ移行(いこう)
  • マイクロサービス、サーバーレス、イベントドリブンアーキテクチャの普及(ふきゅう)
  • TypeScript、Go、Rustなどモダン言語の特性がパターン実装方式を変革(へんかく)
  • フレームワークがパターンを**内蔵(ないぞう)**し、明示的な実装頻度が減少

それでもパターンを知るべき理由:

  1. コードレビューの共通言語 — 「ここにStrategyパターンを適用したら?」の一言で通じる
  2. フレームワーク内部の理解 — React、Spring、Expressはすべてパターンの組み合わせ
  3. 面接必須知識 — FAANG面接でシステム設計と共に頻出(ひんしゅつ)
  4. アーキテクチャ判断力 — いつ使い、いつ使わないべきかの判断

パターンを知ることとパターンを濫用(らんよう)することは違います。この記事ではいつ使い、いつ避けるべきかまで扱います。


2. TOP 10 最も使われるパターン

モダンコードベースでの使用頻度基準:

順位パターン分類使用頻度代表的な使用先
1Strategy振舞い非常に高い決済、認証、ソート
2Observer振舞い非常に高いイベントシステム、React
3Factory Method生成高いDIコンテナ、ORM
4Builder生成高いクエリビルダー、設定
5Decorator構造高いミドルウェア、AOP
6Adapter構造高いAPIラッパー、レガシー統合
7Singleton生成普通設定、ロガー、DB接続プール
8Proxy構造普通キャッシュ、ロギング、アクセス制御
9Command振舞い普通Undo/Redo、CQRS
10State振舞い普通FSM、UI状態管理

3. 生成パターン(Creational Patterns)

3.1 Singleton — 単一インスタンスの保証

問題: グローバル状態(設定、ロガー、DBプール)が複数インスタンス生成されると一貫性(いっかんせい)が崩壊

解決: クラスのインスタンスを一つに制限し、グローバルアクセスポイントを提供

TypeScript — モダン実装:

class AppConfig {
  private static instance: AppConfig;
  private config: Map<string, string> = new Map();

  private constructor() {
    // 外部からの生成をブロック
  }

  static getInstance(): AppConfig {
    if (!AppConfig.instance) {
      AppConfig.instance = new AppConfig();
    }
    return AppConfig.instance;
  }

  get(key: string): string | undefined {
    return this.config.get(key);
  }

  set(key: string, value: string): void {
    this.config.set(key, value);
  }
}

// 使用
const config = AppConfig.getInstance();
config.set('API_URL', 'https://api.example.com');

Python — モジュールレベルSingleton(Pythonic):

# config.py - Pythonではモジュール自体がSingleton
class _AppConfig:
    def __init__(self):
        self._config: dict[str, str] = {}

    def get(self, key: str) -> str | None:
        return self._config.get(key)

    def set(self, key: str, value: str) -> None:
        self._config[key] = value

# モジュールレベルインスタンス = Singleton
app_config = _AppConfig()

使わない方が良い場合:

  • テストが困難になる場合(依存性注入で代替)
  • グローバル状態が不要なのに利便性のために使う場合
  • マルチスレッド環境で同期を考慮していない場合

3.2 Factory Method — オブジェクト生成の委譲(いじょう)

問題: オブジェクト生成ロジックがクライアントに直接結合(けつごう)されると拡張が困難

// 通知システムファクトリー
interface Notification {
  send(message: string): Promise<void>;
}

class EmailNotification implements Notification {
  async send(message: string) {
    console.log(`Email: ${message}`);
  }
}

class SlackNotification implements Notification {
  async send(message: string) {
    console.log(`Slack: ${message}`);
  }
}

class SMSNotification implements Notification {
  async send(message: string) {
    console.log(`SMS: ${message}`);
  }
}

// ファクトリー関数(モダンアプローチ)
type NotificationType = 'email' | 'slack' | 'sms';

function createNotification(type: NotificationType): Notification {
  const notificationMap: Record<NotificationType, () => Notification> = {
    email: () => new EmailNotification(),
    slack: () => new SlackNotification(),
    sms: () => new SMSNotification(),
  };

  const creator = notificationMap[type];
  if (!creator) throw new Error(`Unknown notification type: ${type}`);
  return creator();
}

// 使用
const notifier = createNotification('slack');
await notifier.send('デプロイ完了!');

3.3 Abstract Factory — 関連オブジェクトファミリーの生成

// UIテーマ Abstract Factory
interface Button { render(): string; }
interface Input { render(): string; }

interface UIFactory {
  createButton(): Button;
  createInput(): Input;
}

class DarkThemeFactory implements UIFactory {
  createButton() { return { render: () => '<button class="dark-btn">Click</button>' }; }
  createInput() { return { render: () => '<input class="dark-input" />' }; }
}

class LightThemeFactory implements UIFactory {
  createButton() { return { render: () => '<button class="light-btn">Click</button>' }; }
  createInput() { return { render: () => '<input class="light-input" />' }; }
}

// 使用 — ファクトリーを交換するだけでテーマ全体が変更
function buildUI(factory: UIFactory) {
  const button = factory.createButton();
  const input = factory.createInput();
  return { button: button.render(), input: input.render() };
}

3.4 Builder — 複雑なオブジェクトの段階的構築

問題: コンストラクタの引数が10個以上の「テレスコーピングコンストラクタ」問題

// Fluent Builderパターン
class QueryBuilder {
  private table = '';
  private conditions: string[] = [];
  private orderByField = '';
  private limitCount = 0;

  from(table: string): this {
    this.table = table;
    return this;
  }

  where(condition: string): this {
    this.conditions.push(condition);
    return this;
  }

  orderBy(field: string): this {
    this.orderByField = field;
    return this;
  }

  limit(count: number): this {
    this.limitCount = count;
    return this;
  }

  build(): string {
    let query = `SELECT * FROM ${this.table}`;
    if (this.conditions.length > 0) {
      query += ` WHERE ${this.conditions.join(' AND ')}`;
    }
    if (this.orderByField) {
      query += ` ORDER BY ${this.orderByField}`;
    }
    if (this.limitCount > 0) {
      query += ` LIMIT ${this.limitCount}`;
    }
    return query;
  }
}

// 使用 — 可読性の最大化
const query = new QueryBuilder()
  .from('users')
  .where('age > 18')
  .where('status = "active"')
  .orderBy('created_at DESC')
  .limit(10)
  .build();

3.5 Prototype — 複製による生成

interface Cloneable<T> {
  clone(): T;
}

class GameCharacter implements Cloneable<GameCharacter> {
  constructor(
    public name: string,
    public health: number,
    public inventory: string[],
    public position: { x: number; y: number }
  ) {}

  clone(): GameCharacter {
    return new GameCharacter(
      this.name,
      this.health,
      [...this.inventory],  // ディープコピー
      { ...this.position }   // ディープコピー
    );
  }
}

// プロトタイプレジストリ
const templates = {
  warrior: new GameCharacter('Warrior', 100, ['sword', 'shield'], { x: 0, y: 0 }),
  mage: new GameCharacter('Mage', 70, ['staff', 'potion'], { x: 0, y: 0 }),
};

const player = templates.warrior.clone();
player.name = 'Hero Kim';

4. 構造パターン(Structural Patterns)

4.1 Adapter — 互換性のないインターフェースの接続

// 既存の外部ライブラリ(変更不可)
class LegacyPaymentGateway {
  processPaymentInCents(amountCents: number, curr: string): boolean {
    console.log(`Processing ${amountCents} cents in ${curr}`);
    return true;
  }
}

// 望むインターフェース
interface PaymentProcessor {
  pay(amount: number, currency: string): Promise<boolean>;
}

// Adapter
class PaymentAdapter implements PaymentProcessor {
  constructor(private legacy: LegacyPaymentGateway) {}

  async pay(amount: number, currency: string): Promise<boolean> {
    const cents = Math.round(amount * 100);
    return this.legacy.processPaymentInCents(cents, currency);
  }
}

// 使用 — クライアントはレガシーを知らない
const processor: PaymentProcessor = new PaymentAdapter(
  new LegacyPaymentGateway()
);
await processor.pay(29.99, 'USD');

4.2 Decorator — 動的な機能追加

// ミドルウェアスタイルDecorator
interface HttpHandler {
  handle(request: Request): Promise<Response>;
}

class BaseHandler implements HttpHandler {
  async handle(req: Request): Promise<Response> {
    return new Response('OK', { status: 200 });
  }
}

class LoggingDecorator implements HttpHandler {
  constructor(private inner: HttpHandler) {}

  async handle(req: Request): Promise<Response> {
    console.log(`[LOG] ${req.method} ${req.url}`);
    const start = Date.now();
    const response = await this.inner.handle(req);
    console.log(`[LOG] Completed in ${Date.now() - start}ms`);
    return response;
  }
}

class AuthDecorator implements HttpHandler {
  constructor(private inner: HttpHandler) {}

  async handle(req: Request): Promise<Response> {
    const token = req.headers.get('Authorization');
    if (!token) return new Response('Unauthorized', { status: 401 });
    return this.inner.handle(req);
  }
}

// Decoratorチェーン — 順序が重要!
const handler = new LoggingDecorator(
  new AuthDecorator(
    new BaseHandler()
  )
);

Python デコレータ(言語組み込み機能):

import functools
import time

def log_execution(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - start
        print(f"[LOG] {func.__name__} took {elapsed:.3f}s")
        return result
    return wrapper

def retry(max_attempts: int = 3):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts - 1:
                        raise
                    print(f"Retry {attempt + 1}/{max_attempts}")
        return wrapper
    return decorator

@log_execution
@retry(max_attempts=3)
def fetch_data(url: str) -> dict:
    ...

4.3 Proxy — アクセス制御と追加ロジック

// キャッシュProxy
interface DataService {
  fetchUser(id: string): Promise<User>;
}

class RealDataService implements DataService {
  async fetchUser(id: string): Promise<User> {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
  }
}

class CachingProxy implements DataService {
  private cache = new Map<string, { data: User; expiry: number }>();

  constructor(
    private real: DataService,
    private ttlMs: number = 60000
  ) {}

  async fetchUser(id: string): Promise<User> {
    const cached = this.cache.get(id);
    if (cached && cached.expiry > Date.now()) {
      console.log(`Cache HIT for user ${id}`);
      return cached.data;
    }

    console.log(`Cache MISS for user ${id}`);
    const data = await this.real.fetchUser(id);
    this.cache.set(id, { data, expiry: Date.now() + this.ttlMs });
    return data;
  }
}

4.4 Facade — 複雑なサブシステムのシンプルインターフェース

class VideoEncoder { encode(file: string) { /* ... */ } }
class AudioEncoder { encode(file: string) { /* ... */ } }
class Uploader { upload(file: string, dest: string) { /* ... */ } }
class Notifier { notify(userId: string, msg: string) { /* ... */ } }

// Facade — シンプルなインターフェース
class MediaPublisher {
  private videoEncoder = new VideoEncoder();
  private audioEncoder = new AudioEncoder();
  private uploader = new Uploader();
  private notifier = new Notifier();

  async publish(videoFile: string, userId: string): Promise<string> {
    this.videoEncoder.encode(videoFile);
    this.audioEncoder.encode(videoFile);
    const url = this.uploader.upload(videoFile, 'cdn-bucket');
    this.notifier.notify(userId, '動画アップロード完了!');
    return url;
  }
}

4.5 Composite — ツリー構造の均一処理

interface FileSystemNode {
  name: string;
  getSize(): number;
  print(indent?: string): void;
}

class File implements FileSystemNode {
  constructor(public name: string, private size: number) {}
  getSize(): number { return this.size; }
  print(indent = ''): void {
    console.log(`${indent}FILE ${this.name} (${this.size}B)`);
  }
}

class Directory implements FileSystemNode {
  private children: FileSystemNode[] = [];
  constructor(public name: string) {}
  add(node: FileSystemNode): void { this.children.push(node); }
  getSize(): number {
    return this.children.reduce((sum, child) => sum + child.getSize(), 0);
  }
  print(indent = ''): void {
    console.log(`${indent}DIR ${this.name} (${this.getSize()}B)`);
    this.children.forEach(child => child.print(indent + '  '));
  }
}

5. 振舞いパターン(Behavioral Patterns)

5.1 Strategy — アルゴリズムの交換可能なカプセル化

問題: 決済、ソート、認証でif-else分岐(ぶんき)が無限増殖

// 決済ストラテジー
interface PaymentStrategy {
  pay(amount: number): Promise<PaymentResult>;
  validate(): boolean;
}

class CreditCardPayment implements PaymentStrategy {
  constructor(private cardNumber: string) {}
  validate() { return this.cardNumber.length === 16; }
  async pay(amount: number) {
    return { success: true, method: 'credit_card', amount };
  }
}

class PayPayPayment implements PaymentStrategy {
  constructor(private userId: string) {}
  validate() { return this.userId.length > 0; }
  async pay(amount: number) {
    return { success: true, method: 'paypay', amount };
  }
}

class LinePay implements PaymentStrategy {
  constructor(private token: string) {}
  validate() { return this.token.length > 0; }
  async pay(amount: number) {
    return { success: true, method: 'line_pay', amount };
  }
}

// Context
class PaymentProcessor {
  constructor(private strategy: PaymentStrategy) {}

  setStrategy(strategy: PaymentStrategy) {
    this.strategy = strategy;
  }

  async checkout(amount: number) {
    if (!this.strategy.validate()) {
      throw new Error('Invalid payment method');
    }
    return this.strategy.pay(amount);
  }
}

// ランタイムでストラテジー交換
const processor = new PaymentProcessor(new CreditCardPayment('1234567890123456'));
await processor.checkout(5000);

processor.setStrategy(new PayPayPayment('user-tanaka'));
await processor.checkout(3000);

5.2 Observer — 状態変化の自動伝播(でんぱ)

// タイプセーフなイベントバス
type EventMap = {
  'user:login': { userId: string; timestamp: Date };
  'user:logout': { userId: string };
  'order:created': { orderId: string; total: number };
  'order:shipped': { orderId: string; trackingNumber: string };
};

class TypedEventBus {
  private listeners = new Map<string, Set<Function>>();

  on<K extends keyof EventMap>(
    event: K,
    callback: (data: EventMap[K]) => void
  ): () => void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set());
    }
    this.listeners.get(event)!.add(callback);
    return () => this.listeners.get(event)?.delete(callback);
  }

  emit<K extends keyof EventMap>(event: K, data: EventMap[K]): void {
    this.listeners.get(event)?.forEach(cb => cb(data));
  }
}

// 使用
const bus = new TypedEventBus();
const unsubscribe = bus.on('order:created', (data) => {
  console.log(`新規注文: ${data.orderId}、金額: ${data.total}`);
});

bus.emit('order:created', { orderId: 'ORD-001', total: 50000 });
unsubscribe(); // クリーンアップ

5.3 Command — リクエストのオブジェクト化

interface Command {
  execute(): void;
  undo(): void;
}

class TextEditor {
  content = '';
  private history: Command[] = [];
  private undone: Command[] = [];

  executeCommand(command: Command) {
    command.execute();
    this.history.push(command);
    this.undone = [];
  }

  undo() {
    const command = this.history.pop();
    if (command) {
      command.undo();
      this.undone.push(command);
    }
  }

  redo() {
    const command = this.undone.pop();
    if (command) {
      command.execute();
      this.history.push(command);
    }
  }
}

5.4 State — 状態に基づく振る舞い変化(FSM)

interface OrderState {
  name: string;
  confirm(order: Order): void;
  ship(order: Order): void;
  deliver(order: Order): void;
  cancel(order: Order): void;
}

class PendingState implements OrderState {
  name = 'PENDING';
  confirm(order: Order) { order.setState(new ConfirmedState()); }
  ship() { throw new Error('決済前に配送不可'); }
  deliver() { throw new Error('配送前に受領不可'); }
  cancel(order: Order) { order.setState(new CancelledState()); }
}

class ConfirmedState implements OrderState {
  name = 'CONFIRMED';
  confirm() { throw new Error('既に決済完了'); }
  ship(order: Order) { order.setState(new ShippedState()); }
  deliver() { throw new Error('配送前に受領不可'); }
  cancel(order: Order) { order.setState(new CancelledState()); }
}

class ShippedState implements OrderState {
  name = 'SHIPPED';
  confirm() { throw new Error('既に決済完了'); }
  ship() { throw new Error('既に配送中'); }
  deliver(order: Order) { order.setState(new DeliveredState()); }
  cancel() { throw new Error('配送中はキャンセル不可'); }
}

class DeliveredState implements OrderState {
  name = 'DELIVERED';
  confirm() { throw new Error('完了した注文'); }
  ship() { throw new Error('完了した注文'); }
  deliver() { throw new Error('完了した注文'); }
  cancel() { throw new Error('完了した注文'); }
}

class CancelledState implements OrderState {
  name = 'CANCELLED';
  confirm() { throw new Error('キャンセル済み'); }
  ship() { throw new Error('キャンセル済み'); }
  deliver() { throw new Error('キャンセル済み'); }
  cancel() { throw new Error('既にキャンセル済み'); }
}

class Order {
  private state: OrderState = new PendingState();
  setState(state: OrderState) { this.state = state; }
  getStateName() { return this.state.name; }
  confirm() { this.state.confirm(this); }
  ship() { this.state.ship(this); }
  deliver() { this.state.deliver(this); }
  cancel() { this.state.cancel(this); }
}

5.5 Chain of Responsibility — リクエストの連鎖処理

interface Middleware {
  handle(req: Request, next: () => Promise<Response>): Promise<Response>;
}

class CorsMiddleware implements Middleware {
  async handle(req: Request, next: () => Promise<Response>) {
    const response = await next();
    response.headers.set('Access-Control-Allow-Origin', '*');
    return response;
  }
}

class RateLimitMiddleware implements Middleware {
  private requests = new Map<string, number[]>();

  async handle(req: Request, next: () => Promise<Response>) {
    const ip = req.headers.get('x-forwarded-for') || 'unknown';
    const now = Date.now();
    const timestamps = (this.requests.get(ip) || []).filter(t => t > now - 60000);

    if (timestamps.length >= 100) {
      return new Response('Too Many Requests', { status: 429 });
    }
    timestamps.push(now);
    this.requests.set(ip, timestamps);
    return next();
  }
}

class MiddlewareChain {
  private middlewares: Middleware[] = [];

  use(middleware: Middleware): this {
    this.middlewares.push(middleware);
    return this;
  }

  async execute(req: Request, finalHandler: () => Promise<Response>): Promise<Response> {
    let index = 0;
    const next = async (): Promise<Response> => {
      if (index >= this.middlewares.length) return finalHandler();
      return this.middlewares[index++].handle(req, next);
    };
    return next();
  }
}

6. モダンな代替手段 — 関数型パターン

多くのGoFパターンは関数型プログラミングでより簡潔に表現できます。

GoFパターン関数型の代替説明
Strategy高階関数関数を引数として渡す
ObserverRxJS / Signalsリアクティブストリーム
Commandクロージャ+関数スタック関数自体がCommand
Factoryファクトリー関数クラスなしで関数で生成
Template Method関数合成pipe / compose
Decorator関数ラッピング高階関数でラップ
Singletonモジュールスコープ変数ESモジュールが自体Singleton
// Strategyを関数型で
type SortStrategy<T> = (a: T, b: T) => number;

const byName: SortStrategy<User> = (a, b) => a.name.localeCompare(b.name);
const byAge: SortStrategy<User> = (a, b) => a.age - b.age;

// ストラテジー交換 = 関数引数の変更
const sorted = users.sort(byAge);

// Decoratorを関数型で
const withLogging = <T extends (...args: any[]) => any>(fn: T): T => {
  return ((...args: any[]) => {
    console.log(`Calling ${fn.name} with`, args);
    const result = fn(...args);
    console.log(`Result:`, result);
    return result;
  }) as T;
};

const add = (a: number, b: number) => a + b;
const loggedAdd = withLogging(add);
loggedAdd(1, 2);

依存性注入(DI)— Factory/Singletonのモダンな代替:

interface Logger { log(msg: string): void; }
interface Database { query(sql: string): Promise<any[]>; }

class UserService {
  constructor(
    private logger: Logger,
    private db: Database
  ) {}

  async getUser(id: string) {
    this.logger.log(`Fetching user ${id}`);
    return this.db.query(`SELECT * FROM users WHERE id = '${id}'`);
  }
}

// テストでMockを注入
const mockLogger: Logger = { log: jest.fn() };
const mockDb: Database = { query: jest.fn().mockResolvedValue([]) };
const service = new UserService(mockLogger, mockDb);

7. フレームワーク内のデザインパターン

Reactのパターン

パターンReactでの活用
ObserveruseState、useEffect、状態サブスクリプション
Compositeコンポーネントツリー(JSX)
Strategyレンダープロップ、カスタムフック
HOC(Decorator)withAuth、withTheme
FactorycreateElement
ProxyReact.lazy(遅延ローディング)

Express/Koaのパターン

パターンExpressでの活用
Chain of Responsibilityミドルウェアチェーン
Decoratorapp.use()ミドルウェアラッピング
Strategyルートハンドラー
Adapterbody-parser、corsなど

Springのパターン

パターンSpringでの活用
FactoryApplicationContext(Bean Factory)
ProxyAOP、@Transactional
Template MethodJdbcTemplate、RestTemplate
SingletonデフォルトBeanスコープ
ObserverApplicationEvent

8. アンチパターン — これだけは避けよう

8.1 God Object(神オブジェクト)

すべてのロジックを一つのクラスに詰め込むアンチパターンです。

// BAD - 神オブジェクト
class AppManager {
  createUser() { /* ... */ }
  deleteUser() { /* ... */ }
  processPayment() { /* ... */ }
  sendEmail() { /* ... */ }
  log() { /* ... */ }
  getConfig() { /* ... */ }
  // ... 500行以上
}

// GOOD - 単一責任原則
class UserService { /* ユーザーのみ */ }
class PaymentService { /* 決済のみ */ }
class EmailService { /* メールのみ */ }

8.2 Singletonの濫用

// BAD - テスト不可能なSingleton依存
class OrderService {
  processOrder(orderId: string) {
    const db = DatabaseConnection.getInstance(); // ハードコードされた依存
    const logger = Logger.getInstance();         // テストで交換不可
  }
}

// GOOD - 依存性注入
class OrderService {
  constructor(
    private db: DatabaseConnection,
    private logger: Logger
  ) {}
}

8.3 過度な抽象化(Over-Engineering)

// BAD - 過度な抽象化(YAGNI違反)
interface IStringFormatter { format(s: string): string; }
class UpperCaseFormatter implements IStringFormatter {
  format(s: string) { return s.toUpperCase(); }
}

// GOOD - KISS原則
const toUpper = (s: string) => s.toUpperCase();

9. 面接TOP 10パターン質問

Q1: Singletonの問題点と代替案は?

回答: Singletonはグローバル状態によるテスト困難、隠れた依存性、マルチスレッド同期問題があります。代替として依存性注入(DI)を使用します。DIコンテナがライフサイクルを管理すれば、Singletonの利点を維持しつつテスト可能性を確保できます。

Q2: Strategy vs Stateパターンの違い?

回答: Strategyはアルゴリズム交換が目的でクライアントがストラテジーを選択します。Stateは状態に基づく振る舞い変化が目的で状態オブジェクトが遷移(せんい)を決定します。

Q3: Observerパターンでメモリリークを防ぐには?

回答: 購読解除(unsubscribe)を必ず実装し、WeakRefまたはWeakMapを使用してガベージコレクションを許可します。ReactではuseEffectのcleanup関数で購読解除します。

Q4: Adapter vs Facadeの違い?

回答: Adapterは既存インターフェースを別のインターフェースに変換(1:1変換)。Facadeは複雑なサブシステムにシンプルなインターフェースを提供(N:1簡略化)。

Q5: デザインパターンを使うべきでない時は?

回答: 問題が単純な時、チームメンバーが理解できない過度な抽象化になる時、YAGNI原則に違反する時です。パターンは実際の問題を解決する時のみ導入すべきです。


10. 実践クイズ

Q1: このコードで使われているデザインパターンは?
const handler = new LoggingMiddleware(
  new AuthMiddleware(
    new RateLimitMiddleware(
      new ApiHandler()
    )
  )
);

正解: Decorator + Chain of Responsibility

各ミドルウェアが内部ハンドラーをラップ(Decorator)し、リクエストがチェーンを通じて伝播します(Chain of Responsibility)。

Q2: ReactのuseStateはどのパターンに該当しますか?

正解: Observerパターン

useStateが返すsetter関数を呼び出すと、Reactがその状態を購読しているコンポーネントを自動的に再レンダリングします。これがObserverパターンの核心です。

Q3: Singletonよりもなぜ依存性注入(DI)を好むのか?

正解:

  1. テスト容易性 — DIはMockオブジェクトを簡単に注入可能
  2. 明示的な依存関係 — コンストラクタで依存性が可視化
  3. 柔軟なライフサイクル — リクエストスコープ、セッションスコープなど
  4. SOLID原則準拠 — 依存性逆転原則(DIP)をサポート
Q4: Builderパターンを使うべき時と使うべきでない時は?

使うべき時:

  • コンストラクタの引数が4つ以上
  • オプション引数が多い
  • 不変オブジェクトを段階的に構築
  • 同じ構築プロセスで異なる表現を作成

使うべきでない時:

  • 引数が2-3個以下(過度な抽象化)
  • 単純なDTOやデータクラス
  • 言語にnamed parametersがある場合(Python kwargs、Kotlin named args)
Q5: このシナリオに適切なパターンを選んでください。

「EC2インスタンスの状態(running、stopped、terminated)に応じてstart、stop、terminateメソッドの動作が変わる必要があります。」

正解: Stateパターン

各状態をクラスで表現し、現在の状態オブジェクトが動作を決定します。if-elseで状態を確認する代わりに、状態オブジェクトに動作を委譲します。


11. 参考資料

書籍

  1. "Design Patterns: Elements of Reusable Object-Oriented Software" — GoF(1994)
  2. "Head First Design Patterns" — Freeman & Robson(2020年第2版)
  3. "Patterns of Enterprise Application Architecture" — Martin Fowler
  4. "Refactoring to Patterns" — Joshua Kerievsky
  5. "A Philosophy of Software Design" — John Ousterhout

オンラインリソース

  1. Refactoring.Guru — Design Patterns — ビジュアルパターンカタログ
  2. Source Making — Design Patterns
  3. TypeScript Design Patterns
  4. Python Design Patterns
  5. Go Design Patterns

講義と動画

  1. Christopher Okhravi — Design Patterns
  2. Derek Banas — Design Patterns Tutorial

アーキテクチャパターン

  1. Martin Fowler — Patterns of Enterprise Architecture
  2. Microsoft — Cloud Design Patterns
  3. Microservices Patterns — Chris Richardson
  4. Game Programming Patterns — Robert Nystrom(無料ウェブブック)