- Authors

- Name
- Youngju Kim
- @fjvbn20031
目次
1. 2025年にデザインパターンが重要な理由
1994年にGang of Four(GoF)が発表した23個のデザインパターンは、30年経った今でもソフトウェアエンジニアリングの共通言語です。しかし、世界は変わりました。
モダン開発環境の変化:
- OOP中心から関数型(かんすうがた)+リアクティブのハイブリッドへ移行(いこう)
- マイクロサービス、サーバーレス、イベントドリブンアーキテクチャの普及(ふきゅう)
- TypeScript、Go、Rustなどモダン言語の特性がパターン実装方式を変革(へんかく)
- フレームワークがパターンを**内蔵(ないぞう)**し、明示的な実装頻度が減少
それでもパターンを知るべき理由:
- コードレビューの共通言語 — 「ここにStrategyパターンを適用したら?」の一言で通じる
- フレームワーク内部の理解 — React、Spring、Expressはすべてパターンの組み合わせ
- 面接必須知識 — FAANG面接でシステム設計と共に頻出(ひんしゅつ)
- アーキテクチャ判断力 — いつ使い、いつ使わないべきかの判断
パターンを知ることとパターンを濫用(らんよう)することは違います。この記事ではいつ使い、いつ避けるべきかまで扱います。
2. TOP 10 最も使われるパターン
モダンコードベースでの使用頻度基準:
| 順位 | パターン | 分類 | 使用頻度 | 代表的な使用先 |
|---|---|---|---|---|
| 1 | Strategy | 振舞い | 非常に高い | 決済、認証、ソート |
| 2 | Observer | 振舞い | 非常に高い | イベントシステム、React |
| 3 | Factory Method | 生成 | 高い | DIコンテナ、ORM |
| 4 | Builder | 生成 | 高い | クエリビルダー、設定 |
| 5 | Decorator | 構造 | 高い | ミドルウェア、AOP |
| 6 | Adapter | 構造 | 高い | APIラッパー、レガシー統合 |
| 7 | Singleton | 生成 | 普通 | 設定、ロガー、DB接続プール |
| 8 | Proxy | 構造 | 普通 | キャッシュ、ロギング、アクセス制御 |
| 9 | Command | 振舞い | 普通 | Undo/Redo、CQRS |
| 10 | State | 振舞い | 普通 | 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 | 高階関数 | 関数を引数として渡す |
| Observer | RxJS / 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での活用 |
|---|---|
| Observer | useState、useEffect、状態サブスクリプション |
| Composite | コンポーネントツリー(JSX) |
| Strategy | レンダープロップ、カスタムフック |
| HOC(Decorator) | withAuth、withTheme |
| Factory | createElement |
| Proxy | React.lazy(遅延ローディング) |
Express/Koaのパターン
| パターン | Expressでの活用 |
|---|---|
| Chain of Responsibility | ミドルウェアチェーン |
| Decorator | app.use()ミドルウェアラッピング |
| Strategy | ルートハンドラー |
| Adapter | body-parser、corsなど |
Springのパターン
| パターン | Springでの活用 |
|---|---|
| Factory | ApplicationContext(Bean Factory) |
| Proxy | AOP、@Transactional |
| Template Method | JdbcTemplate、RestTemplate |
| Singleton | デフォルトBeanスコープ |
| Observer | ApplicationEvent |
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)を好むのか?
正解:
- テスト容易性 — DIはMockオブジェクトを簡単に注入可能
- 明示的な依存関係 — コンストラクタで依存性が可視化
- 柔軟なライフサイクル — リクエストスコープ、セッションスコープなど
- 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. 参考資料
書籍
- "Design Patterns: Elements of Reusable Object-Oriented Software" — GoF(1994)
- "Head First Design Patterns" — Freeman & Robson(2020年第2版)
- "Patterns of Enterprise Application Architecture" — Martin Fowler
- "Refactoring to Patterns" — Joshua Kerievsky
- "A Philosophy of Software Design" — John Ousterhout
オンラインリソース
- Refactoring.Guru — Design Patterns — ビジュアルパターンカタログ
- Source Making — Design Patterns
- TypeScript Design Patterns
- Python Design Patterns
- Go Design Patterns