puzzle

モダン開発で使えるデザインパターン実践ガイド - GoF to 2025

2025.12.02

デザインパターンは、ソフトウェア設計における再利用可能な解決策のカタログです。1994年のGoF(Gang of Four)本から30年、パターンの本質は変わらずとも、その適用方法は大きく進化しました。本記事では、モダンな開発環境での実践的なパターン活用法を解説します。

デザインパターンの分類

flowchart TB
    subgraph DP["デザインパターン"]
        subgraph Creational["生成パターン"]
            C1["Singleton"]
            C2["Factory Method"]
            C3["Abstract Factory"]
            C4["Builder"]
            C5["Prototype"]
        end

        subgraph Structural["構造パターン"]
            S1["Adapter"]
            S2["Bridge"]
            S3["Composite"]
            S4["Decorator"]
            S5["Facade"]
            S6["Flyweight"]
            S7["Proxy"]
        end

        subgraph Behavioral["振る舞いパターン"]
            B1["Strategy"]
            B2["Observer"]
            B3["Command"]
            B4["State"]
            B5["Template Method"]
            B6["Iterator"]
            B7["Mediator"]
            B8["Memento"]
            B9["Visitor"]
            B10["Chain of Resp."]
        end
    end

生成パターン

Factory Method パターン

オブジェクトの生成をサブクラスに委譲し、生成ロジックを分離します。

// モダンTypeScriptでのFactory Method実装

// 製品インターフェース
interface Notification {
  send(message: string): Promise<void>;
}

// 具体的な製品
class EmailNotification implements Notification {
  constructor(private email: string) {}

  async send(message: string): Promise<void> {
    console.log(`Email to ${this.email}: ${message}`);
    // 実際のメール送信ロジック
  }
}

class SlackNotification implements Notification {
  constructor(private webhookUrl: string) {}

  async send(message: string): Promise<void> {
    console.log(`Slack webhook: ${message}`);
    // Slack API呼び出し
  }
}

class SMSNotification implements Notification {
  constructor(private phoneNumber: string) {}

  async send(message: string): Promise<void> {
    console.log(`SMS to ${this.phoneNumber}: ${message}`);
    // SMS送信API呼び出し
  }
}

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

interface NotificationConfig {
  type: NotificationType;
  email?: string;
  webhookUrl?: string;
  phoneNumber?: string;
}

function createNotification(config: NotificationConfig): Notification {
  switch (config.type) {
    case 'email':
      if (!config.email) throw new Error('Email required');
      return new EmailNotification(config.email);
    case 'slack':
      if (!config.webhookUrl) throw new Error('Webhook URL required');
      return new SlackNotification(config.webhookUrl);
    case 'sms':
      if (!config.phoneNumber) throw new Error('Phone number required');
      return new SMSNotification(config.phoneNumber);
    default:
      throw new Error(`Unknown notification type: ${config.type}`);
  }
}

// 使用例
const notification = createNotification({
  type: 'email',
  email: 'user@example.com'
});
await notification.send('Hello!');

Builder パターン

複雑なオブジェクトの構築を段階的に行います。

// Builderパターン - Fluent API実装

interface HttpRequestConfig {
  method: 'GET' | 'POST' | 'PUT' | 'DELETE';
  url: string;
  headers: Record<string, string>;
  body?: unknown;
  timeout: number;
  retries: number;
}

class HttpRequestBuilder {
  private config: Partial<HttpRequestConfig> = {
    method: 'GET',
    headers: {},
    timeout: 30000,
    retries: 0,
  };

  url(url: string): this {
    this.config.url = url;
    return this;
  }

  method(method: HttpRequestConfig['method']): this {
    this.config.method = method;
    return this;
  }

  header(key: string, value: string): this {
    this.config.headers = {
      ...this.config.headers,
      [key]: value,
    };
    return this;
  }

  authorization(token: string): this {
    return this.header('Authorization', `Bearer ${token}`);
  }

  contentType(type: string): this {
    return this.header('Content-Type', type);
  }

  json(data: unknown): this {
    this.config.body = data;
    return this.contentType('application/json');
  }

  timeout(ms: number): this {
    this.config.timeout = ms;
    return this;
  }

  retries(count: number): this {
    this.config.retries = count;
    return this;
  }

  build(): HttpRequestConfig {
    if (!this.config.url) {
      throw new Error('URL is required');
    }
    return this.config as HttpRequestConfig;
  }

  // 便利メソッド - 直接実行
  async execute<T>(): Promise<T> {
    const config = this.build();
    // 実際のfetch実行ロジック
    const response = await fetch(config.url, {
      method: config.method,
      headers: config.headers,
      body: config.body ? JSON.stringify(config.body) : undefined,
    });
    return response.json();
  }
}

// 使用例
const response = await new HttpRequestBuilder()
  .url('https://api.example.com/users')
  .method('POST')
  .authorization('my-token')
  .json({ name: 'John', email: 'john@example.com' })
  .timeout(5000)
  .retries(3)
  .execute<{ id: string }>();

Singleton パターン(モダンな代替案)

従来のSingletonは避け、DIコンテナやモジュールスコープを活用します。

// ❌ 従来のSingleton(避けるべき)
class LegacySingleton {
  private static instance: LegacySingleton;

  private constructor() {}

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

// ✅ モジュールスコープでのシングルトン
// database.ts
class DatabaseConnection {
  constructor(private connectionString: string) {}

  async query<T>(sql: string): Promise<T[]> {
    // クエリ実行
    return [];
  }
}

// モジュールレベルでエクスポート
export const db = new DatabaseConnection(process.env.DATABASE_URL!);

// ✅ DIコンテナを使用(推奨)
// container.ts
import { Container } from 'inversify';

const container = new Container();
container.bind<DatabaseConnection>('Database')
  .to(DatabaseConnection)
  .inSingletonScope();

export { container };

構造パターン

Adapter パターン

互換性のないインターフェース間を橋渡しします。

// レガシーAPIを新しいインターフェースに適合させる

// 既存のレガシーシステム
interface LegacyPaymentSystem {
  processPayment(
    amount: number,
    cardNumber: string,
    expiry: string,
    cvv: string
  ): boolean;
}

class LegacyStripePayment implements LegacyPaymentSystem {
  processPayment(
    amount: number,
    cardNumber: string,
    expiry: string,
    cvv: string
  ): boolean {
    console.log('Processing via legacy Stripe...');
    return true;
  }
}

// 新しいインターフェース
interface PaymentGateway {
  charge(payment: PaymentDetails): Promise<PaymentResult>;
}

interface PaymentDetails {
  amount: number;
  currency: string;
  card: {
    number: string;
    expiryMonth: number;
    expiryYear: number;
    cvc: string;
  };
}

interface PaymentResult {
  success: boolean;
  transactionId: string;
  error?: string;
}

// アダプター
class LegacyPaymentAdapter implements PaymentGateway {
  constructor(private legacySystem: LegacyPaymentSystem) {}

  async charge(payment: PaymentDetails): Promise<PaymentResult> {
    const expiry = `${payment.card.expiryMonth}/${payment.card.expiryYear}`;

    const success = this.legacySystem.processPayment(
      payment.amount,
      payment.card.number,
      expiry,
      payment.card.cvc
    );

    return {
      success,
      transactionId: success ? crypto.randomUUID() : '',
      error: success ? undefined : 'Payment failed',
    };
  }
}

// 使用例
const legacyStripe = new LegacyStripePayment();
const paymentGateway: PaymentGateway = new LegacyPaymentAdapter(legacyStripe);

const result = await paymentGateway.charge({
  amount: 1000,
  currency: 'JPY',
  card: {
    number: '4242424242424242',
    expiryMonth: 12,
    expiryYear: 2025,
    cvc: '123',
  },
});

Decorator パターン

既存オブジェクトに動的に機能を追加します。

// TypeScriptデコレータを使用した実装

// メソッドデコレータ - ログ出力
function Log(
  target: object,
  propertyKey: string,
  descriptor: PropertyDescriptor
): PropertyDescriptor {
  const originalMethod = descriptor.value;

  descriptor.value = async function (...args: unknown[]) {
    console.log(`[${propertyKey}] Called with:`, args);
    const start = performance.now();

    try {
      const result = await originalMethod.apply(this, args);
      const duration = performance.now() - start;
      console.log(`[${propertyKey}] Returned:`, result, `(${duration}ms)`);
      return result;
    } catch (error) {
      console.error(`[${propertyKey}] Error:`, error);
      throw error;
    }
  };

  return descriptor;
}

// キャッシュデコレータ
function Cache(ttlMs: number = 60000) {
  const cache = new Map<string, { value: unknown; expiry: number }>();

  return function (
    target: object,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ): PropertyDescriptor {
    const originalMethod = descriptor.value;

    descriptor.value = async function (...args: unknown[]) {
      const key = JSON.stringify(args);
      const cached = cache.get(key);

      if (cached && cached.expiry > Date.now()) {
        console.log(`[Cache Hit] ${propertyKey}`);
        return cached.value;
      }

      const result = await originalMethod.apply(this, args);
      cache.set(key, { value: result, expiry: Date.now() + ttlMs });
      return result;
    };

    return descriptor;
  };
}

// リトライデコレータ
function Retry(maxAttempts: number = 3, delayMs: number = 1000) {
  return function (
    target: object,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ): PropertyDescriptor {
    const originalMethod = descriptor.value;

    descriptor.value = async function (...args: unknown[]) {
      let lastError: Error;

      for (let attempt = 1; attempt <= maxAttempts; attempt++) {
        try {
          return await originalMethod.apply(this, args);
        } catch (error) {
          lastError = error as Error;
          console.warn(`[Retry] Attempt ${attempt}/${maxAttempts} failed`);

          if (attempt < maxAttempts) {
            await new Promise(resolve => setTimeout(resolve, delayMs));
          }
        }
      }

      throw lastError!;
    };

    return descriptor;
  };
}

// デコレータの適用
class UserService {
  @Log
  @Cache(30000)  // 30秒キャッシュ
  @Retry(3, 1000)
  async getUser(id: string): Promise<User> {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
  }
}

Proxy パターン

オブジェクトへのアクセスを制御します。

// ES6 Proxyを使用した実装

interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

// バリデーションプロキシ
function createValidatedUser(user: User): User {
  return new Proxy(user, {
    set(target, property, value) {
      if (property === 'email') {
        if (typeof value !== 'string' || !value.includes('@')) {
          throw new Error('Invalid email format');
        }
      }

      if (property === 'role') {
        if (!['admin', 'user'].includes(value)) {
          throw new Error('Invalid role');
        }
      }

      return Reflect.set(target, property, value);
    },
  });
}

// 遅延ロードプロキシ
function createLazyLoader<T extends object>(
  loader: () => Promise<T>
): T {
  let instance: T | null = null;
  let loading: Promise<T> | null = null;

  return new Proxy({} as T, {
    get(target, property) {
      if (!instance) {
        if (!loading) {
          loading = loader().then(loaded => {
            instance = loaded;
            return loaded;
          });
        }
        // プロミスを返すか、ブロックするか選択
        return loading.then(inst => (inst as any)[property]);
      }
      return (instance as any)[property];
    },
  });
}

// アクセス制御プロキシ
function createSecureObject<T extends object>(
  obj: T,
  currentUser: User
): T {
  return new Proxy(obj, {
    get(target, property) {
      const value = Reflect.get(target, property);

      // 管理者限定プロパティ
      const adminOnlyProps = ['password', 'secretKey', 'apiToken'];
      if (adminOnlyProps.includes(String(property))) {
        if (currentUser.role !== 'admin') {
          throw new Error('Access denied: Admin only');
        }
      }

      return value;
    },

    set(target, property, value) {
      if (currentUser.role !== 'admin') {
        throw new Error('Access denied: Read only');
      }
      return Reflect.set(target, property, value);
    },
  });
}

振る舞いパターン

Strategy パターン

アルゴリズムをカプセル化し、交換可能にします。

// 料金計算ストラテジー

interface PricingStrategy {
  calculatePrice(basePrice: number, quantity: number): number;
  getName(): string;
}

class RegularPricing implements PricingStrategy {
  calculatePrice(basePrice: number, quantity: number): number {
    return basePrice * quantity;
  }
  getName(): string {
    return 'Regular';
  }
}

class BulkPricing implements PricingStrategy {
  constructor(private discountThreshold: number, private discountRate: number) {}

  calculatePrice(basePrice: number, quantity: number): number {
    if (quantity >= this.discountThreshold) {
      return basePrice * quantity * (1 - this.discountRate);
    }
    return basePrice * quantity;
  }

  getName(): string {
    return `Bulk (${this.discountRate * 100}% off for ${this.discountThreshold}+)`;
  }
}

class SubscriberPricing implements PricingStrategy {
  constructor(private memberDiscountRate: number) {}

  calculatePrice(basePrice: number, quantity: number): number {
    return basePrice * quantity * (1 - this.memberDiscountRate);
  }

  getName(): string {
    return `Subscriber (${this.memberDiscountRate * 100}% off)`;
  }
}

class SeasonalPricing implements PricingStrategy {
  constructor(
    private seasonalMultiplier: number,
    private seasonName: string
  ) {}

  calculatePrice(basePrice: number, quantity: number): number {
    return basePrice * quantity * this.seasonalMultiplier;
  }

  getName(): string {
    return `Seasonal - ${this.seasonName}`;
  }
}

// コンテキスト
class ShoppingCart {
  private items: Array<{ name: string; price: number; quantity: number }> = [];
  private pricingStrategy: PricingStrategy = new RegularPricing();

  addItem(name: string, price: number, quantity: number): void {
    this.items.push({ name, price, quantity });
  }

  setPricingStrategy(strategy: PricingStrategy): void {
    this.pricingStrategy = strategy;
  }

  calculateTotal(): number {
    return this.items.reduce((total, item) => {
      return total + this.pricingStrategy.calculatePrice(item.price, item.quantity);
    }, 0);
  }

  getReceipt(): string {
    const lines = this.items.map(item => {
      const subtotal = this.pricingStrategy.calculatePrice(item.price, item.quantity);
      return `${item.name} x${item.quantity}: ¥${subtotal}`;
    });

    return [
      `Pricing: ${this.pricingStrategy.getName()}`,
      '---',
      ...lines,
      '---',
      `Total: ¥${this.calculateTotal()}`,
    ].join('\n');
  }
}

// 使用例
const cart = new ShoppingCart();
cart.addItem('Widget', 1000, 5);
cart.addItem('Gadget', 2000, 3);

console.log(cart.getReceipt());
// Pricing: Regular
// Total: ¥11000

cart.setPricingStrategy(new BulkPricing(3, 0.15));
console.log(cart.getReceipt());
// Pricing: Bulk (15% off for 3+)
// Total: ¥9350

Observer パターン

オブジェクト間の一対多の依存関係を定義します。

// TypeScriptでの型安全なObserver実装

type EventMap = {
  userCreated: { id: string; email: string };
  userUpdated: { id: string; changes: Partial<User> };
  userDeleted: { id: string };
  orderPlaced: { orderId: string; userId: string; total: number };
};

type EventKey = keyof EventMap;
type EventHandler<K extends EventKey> = (event: EventMap[K]) => void | Promise<void>;

class TypedEventEmitter {
  private handlers = new Map<EventKey, Set<EventHandler<any>>>();

  on<K extends EventKey>(event: K, handler: EventHandler<K>): () => void {
    if (!this.handlers.has(event)) {
      this.handlers.set(event, new Set());
    }
    this.handlers.get(event)!.add(handler);

    // 購読解除関数を返す
    return () => this.off(event, handler);
  }

  off<K extends EventKey>(event: K, handler: EventHandler<K>): void {
    this.handlers.get(event)?.delete(handler);
  }

  async emit<K extends EventKey>(event: K, data: EventMap[K]): Promise<void> {
    const eventHandlers = this.handlers.get(event);
    if (!eventHandlers) return;

    const promises = Array.from(eventHandlers).map(handler =>
      Promise.resolve(handler(data))
    );

    await Promise.all(promises);
  }

  once<K extends EventKey>(event: K, handler: EventHandler<K>): () => void {
    const wrappedHandler: EventHandler<K> = async (data) => {
      this.off(event, wrappedHandler);
      await handler(data);
    };
    return this.on(event, wrappedHandler);
  }
}

// 使用例
const eventBus = new TypedEventEmitter();

// メール送信サービス
eventBus.on('userCreated', async ({ email }) => {
  console.log(`Sending welcome email to ${email}`);
});

// 分析サービス
eventBus.on('userCreated', ({ id }) => {
  console.log(`Tracking user creation: ${id}`);
});

// 注文処理
eventBus.on('orderPlaced', async ({ orderId, userId, total }) => {
  console.log(`Processing order ${orderId} for user ${userId}: ¥${total}`);
});

// イベント発火
await eventBus.emit('userCreated', {
  id: 'user-123',
  email: 'user@example.com',
});

Command パターン

操作をオブジェクトとしてカプセル化します。

// Undo/Redo機能を持つCommand実装

interface Command {
  execute(): Promise<void>;
  undo(): Promise<void>;
  getDescription(): string;
}

// テキストエディタのコマンド例
class TextEditor {
  private content: string = '';

  getContent(): string {
    return this.content;
  }

  setContent(content: string): void {
    this.content = content;
  }

  insertAt(position: number, text: string): void {
    this.content =
      this.content.slice(0, position) + text + this.content.slice(position);
  }

  deleteRange(start: number, end: number): string {
    const deleted = this.content.slice(start, end);
    this.content = this.content.slice(0, start) + this.content.slice(end);
    return deleted;
  }
}

class InsertTextCommand implements Command {
  constructor(
    private editor: TextEditor,
    private position: number,
    private text: string
  ) {}

  async execute(): Promise<void> {
    this.editor.insertAt(this.position, this.text);
  }

  async undo(): Promise<void> {
    this.editor.deleteRange(this.position, this.position + this.text.length);
  }

  getDescription(): string {
    return `Insert "${this.text}" at position ${this.position}`;
  }
}

class DeleteTextCommand implements Command {
  private deletedText: string = '';

  constructor(
    private editor: TextEditor,
    private start: number,
    private end: number
  ) {}

  async execute(): Promise<void> {
    this.deletedText = this.editor.deleteRange(this.start, this.end);
  }

  async undo(): Promise<void> {
    this.editor.insertAt(this.start, this.deletedText);
  }

  getDescription(): string {
    return `Delete from ${this.start} to ${this.end}`;
  }
}

// コマンドマネージャー(Invoker)
class CommandManager {
  private history: Command[] = [];
  private redoStack: Command[] = [];

  async execute(command: Command): Promise<void> {
    await command.execute();
    this.history.push(command);
    this.redoStack = [];  // 新しいコマンド実行でredoスタックをクリア
  }

  async undo(): Promise<boolean> {
    const command = this.history.pop();
    if (!command) return false;

    await command.undo();
    this.redoStack.push(command);
    return true;
  }

  async redo(): Promise<boolean> {
    const command = this.redoStack.pop();
    if (!command) return false;

    await command.execute();
    this.history.push(command);
    return true;
  }

  getHistory(): string[] {
    return this.history.map(cmd => cmd.getDescription());
  }
}

// 使用例
const editor = new TextEditor();
const manager = new CommandManager();

await manager.execute(new InsertTextCommand(editor, 0, 'Hello '));
await manager.execute(new InsertTextCommand(editor, 6, 'World!'));
console.log(editor.getContent()); // "Hello World!"

await manager.undo();
console.log(editor.getContent()); // "Hello "

await manager.redo();
console.log(editor.getContent()); // "Hello World!"

現代的なアーキテクチャパターン

Repository パターン

データアクセスロジックを抽象化します。

// Repositoryパターン with TypeScript

interface Entity {
  id: string;
}

interface Repository<T extends Entity> {
  findById(id: string): Promise<T | null>;
  findAll(): Promise<T[]>;
  findBy(criteria: Partial<T>): Promise<T[]>;
  save(entity: T): Promise<T>;
  delete(id: string): Promise<boolean>;
}

interface User extends Entity {
  id: string;
  email: string;
  name: string;
  createdAt: Date;
}

// インメモリ実装(テスト用)
class InMemoryUserRepository implements Repository<User> {
  private users: Map<string, User> = new Map();

  async findById(id: string): Promise<User | null> {
    return this.users.get(id) || null;
  }

  async findAll(): Promise<User[]> {
    return Array.from(this.users.values());
  }

  async findBy(criteria: Partial<User>): Promise<User[]> {
    return Array.from(this.users.values()).filter(user =>
      Object.entries(criteria).every(
        ([key, value]) => user[key as keyof User] === value
      )
    );
  }

  async save(entity: User): Promise<User> {
    this.users.set(entity.id, entity);
    return entity;
  }

  async delete(id: string): Promise<boolean> {
    return this.users.delete(id);
  }
}

// Prisma実装(本番用)
class PrismaUserRepository implements Repository<User> {
  constructor(private prisma: PrismaClient) {}

  async findById(id: string): Promise<User | null> {
    return this.prisma.user.findUnique({ where: { id } });
  }

  async findAll(): Promise<User[]> {
    return this.prisma.user.findMany();
  }

  async findBy(criteria: Partial<User>): Promise<User[]> {
    return this.prisma.user.findMany({ where: criteria });
  }

  async save(entity: User): Promise<User> {
    return this.prisma.user.upsert({
      where: { id: entity.id },
      update: entity,
      create: entity,
    });
  }

  async delete(id: string): Promise<boolean> {
    try {
      await this.prisma.user.delete({ where: { id } });
      return true;
    } catch {
      return false;
    }
  }
}

Unit of Work パターン

トランザクション内の変更を追跡・管理します。

// Unit of Work実装

interface UnitOfWork {
  begin(): Promise<void>;
  commit(): Promise<void>;
  rollback(): Promise<void>;
  userRepository: Repository<User>;
  orderRepository: Repository<Order>;
}

class PrismaUnitOfWork implements UnitOfWork {
  private transaction: Prisma.TransactionClient | null = null;
  private _userRepository: Repository<User> | null = null;
  private _orderRepository: Repository<Order> | null = null;

  constructor(private prisma: PrismaClient) {}

  async begin(): Promise<void> {
    // Prismaの対話型トランザクション
    return new Promise((resolve) => {
      this.prisma.$transaction(async (tx) => {
        this.transaction = tx;
        resolve();
        // トランザクションは commit/rollback まで保持
      });
    });
  }

  get userRepository(): Repository<User> {
    if (!this._userRepository) {
      this._userRepository = new PrismaUserRepository(
        this.transaction || this.prisma
      );
    }
    return this._userRepository;
  }

  get orderRepository(): Repository<Order> {
    if (!this._orderRepository) {
      this._orderRepository = new PrismaOrderRepository(
        this.transaction || this.prisma
      );
    }
    return this._orderRepository;
  }

  async commit(): Promise<void> {
    // Prismaトランザクションは自動コミット
    this.transaction = null;
  }

  async rollback(): Promise<void> {
    throw new Error('Rollback requested');
  }
}

// 使用例
async function createOrderWithUser(uow: UnitOfWork) {
  await uow.begin();

  try {
    const user = await uow.userRepository.save({
      id: crypto.randomUUID(),
      email: 'new@example.com',
      name: 'New User',
      createdAt: new Date(),
    });

    const order = await uow.orderRepository.save({
      id: crypto.randomUUID(),
      userId: user.id,
      total: 5000,
      status: 'pending',
    });

    await uow.commit();
    return { user, order };
  } catch (error) {
    await uow.rollback();
    throw error;
  }
}

SOLID原則との関係

flowchart LR
    subgraph SOLID["SOLID原則"]
        S["S - 単一責任原則 (SRP)"]
        O["O - オープン・クローズド原則 (OCP)"]
        L["L - リスコフの置換原則 (LSP)"]
        I["I - インターフェース分離原則 (ISP)"]
        D["D - 依存性逆転原則 (DIP)"]
    end

    subgraph Patterns["関連パターン"]
        P1["Factory, Strategy, Command"]
        P2["Strategy, Decorator, Template Method"]
        P3["Factory Method, Abstract Factory"]
        P4["Adapter, Facade"]
        P5["Repository, Dependency Injection"]
    end

    S --> P1
    O --> P2
    L --> P3
    I --> P4
    D --> P5

まとめ

デザインパターンは、問題解決のための共通言語であり、チーム開発における認識合わせにも役立ちます。

選定の指針

目的推奨パターン
オブジェクト生成の柔軟性Factory, Builder
既存コードの拡張Decorator, Adapter
アルゴリズムの切り替えStrategy
イベント駆動Observer
操作の取り消しCommand
データアクセス抽象化Repository

モダン開発でのベストプラクティス

  1. パターンの過剰適用を避ける - シンプルな問題にはシンプルな解決策
  2. 言語機能を活用 - TypeScript/Pythonの型システム、デコレータ
  3. テスタビリティを重視 - DIを活用し依存を注入可能に
  4. 関数型アプローチとの併用 - 状態を持たない処理は関数で

デザインパターンは目的ではなく手段です。問題を理解し、適切なパターンを選択することが重要です。

参考リンク

← 一覧に戻る