信頼度ランク
| S | 公式ソース確認済み |
| A | 成功実績多数・失敗例少数 |
| B | 賛否両論 |
| C | 動作未確認・セキュリティリスク高 |
| Z | 個人所感 |
デザインパターン入門:実務でよく使うGoFパターン5選
GoFデザインパターン23種の中から実務で特に頻出する5つを厳選。Strategy・Observer・Factory・Decorator・Commandをコード例付きで解説します。
一言結論
デザインパターンは「解法の名前」を共有するための語彙であり、Strategy・Observer・Factoryの3パターンだけでも習得すれば、コードレビューや設計議論での意思疎通コストが劇的に下がる。
GoFパターンとは
GoF(Gang of Four)の書籍『Design Patterns』(1994)で定義された23種のオブジェクト指向設計パターン。頻出する設計上の問題に名前をつけた共通語彙として、今もコードレビューや技術議論で使われている。
3カテゴリに分類:
- 生成 (Creational): オブジェクト生成の方法
- 構造 (Structural): クラスの組み合わせ方
- 振る舞い (Behavioral): オブジェクト間の責務分担
1. Strategy パターン(振る舞い)
「アルゴリズムを差し替え可能にする」
// 課題: 支払い方法・ソートアルゴリズム・認証方式など、
// 処理の「中身」だけを切り替えたい
interface SortStrategy {
sort(data: number[]): number[];
}
class BubbleSort implements SortStrategy {
sort(data: number[]): number[] {
// バブルソートの実装
return [...data].sort((a, b) => a - b); // 簡略化
}
}
class QuickSort implements SortStrategy {
sort(data: number[]): number[] {
// クイックソートの実装
return [...data].sort((a, b) => a - b); // 簡略化
}
}
class Sorter {
constructor(private strategy: SortStrategy) {}
// 実行時にストラテジーを切り替え可能
setStrategy(strategy: SortStrategy) {
this.strategy = strategy;
}
sort(data: number[]): number[] {
return this.strategy.sort(data);
}
}
const sorter = new Sorter(new BubbleSort());
sorter.sort([3, 1, 4, 1, 5]);
// データが多い場合はQuickSortに切り替え
sorter.setStrategy(new QuickSort());
sorter.sort(largeDataSet);
実務での使いどころ: 支払い方法・ファイルエクスポート形式(CSV/JSON/PDF)・認証プロバイダーの切り替え
2. Observer パターン(振る舞い)
「イベントの発行者と受信者を疎結合にする」
// 課題: 注文が完了したら、在庫更新・メール送信・ポイント付与など
// 複数の処理を呼び出したいが、注文処理に直接書きたくない
interface Observer<T> {
update(event: T): void;
}
class EventEmitter<T> {
private observers: Observer<T>[] = [];
subscribe(observer: Observer<T>) {
this.observers.push(observer);
}
unsubscribe(observer: Observer<T>) {
this.observers = this.observers.filter(o => o !== observer);
}
emit(event: T) {
this.observers.forEach(observer => observer.update(event));
}
}
// イベントの型
interface OrderEvent {
orderId: string;
userId: string;
total: number;
}
// 各オブザーバー
class InventoryObserver implements Observer<OrderEvent> {
update(event: OrderEvent) {
console.log(`在庫更新: 注文${event.orderId}`);
}
}
class EmailObserver implements Observer<OrderEvent> {
update(event: OrderEvent) {
console.log(`メール送信 to user:${event.userId}`);
}
}
// 使用
const orderEmitter = new EventEmitter<OrderEvent>();
orderEmitter.subscribe(new InventoryObserver());
orderEmitter.subscribe(new EmailObserver());
// 注文完了時にemitするだけ(追加の処理はsubscribeするだけ)
orderEmitter.emit({ orderId: "ORD001", userId: "user1", total: 5000 });
Node.js の
EventEmitter、DOM のaddEventListener、RxJSのObservableはすべてObserverパターンの実装。
3. Factory / Factory Method パターン(生成)
「オブジェクト生成のロジックを隠蔽する」
// 課題: 条件に応じて異なるクラスのインスタンスを生成したいが、
// 呼び出し側に生成の詳細を知らせたくない
interface Notification {
send(message: string): Promise<void>;
}
class EmailNotification implements Notification {
constructor(private address: string) {}
async send(message: string) {
console.log(`Email to ${this.address}: ${message}`);
}
}
class SlackNotification implements Notification {
constructor(private webhookUrl: string) {}
async send(message: string) {
console.log(`Slack to ${this.webhookUrl}: ${message}`);
}
}
class SMSNotification implements Notification {
constructor(private phoneNumber: string) {}
async send(message: string) {
console.log(`SMS to ${this.phoneNumber}: ${message}`);
}
}
// Factory: 生成ロジックを集約
class NotificationFactory {
static create(config: { type: string; target: string }): Notification {
switch (config.type) {
case "email": return new EmailNotification(config.target);
case "slack": return new SlackNotification(config.target);
case "sms": return new SMSNotification(config.target);
default: throw new Error(`Unknown notification type: ${config.type}`);
}
}
}
// 呼び出し側はtypeを指定するだけ
const notifier = NotificationFactory.create({ type: "slack", target: "https://hooks.slack.com/..." });
await notifier.send("デプロイ完了!");
4. Decorator パターン(構造)
「既存クラスを変更せずに機能を動的に追加する」
// 課題: ログ・キャッシュ・認証チェックなど横断的な処理を
// 本来の処理に埋め込まずに追加したい
interface DataFetcher {
fetch(id: string): Promise<unknown>;
}
class UserFetcher implements DataFetcher {
async fetch(id: string) {
// DBからユーザー取得(本来の処理)
return { id, name: "Alice" };
}
}
// ログを追加するデコレータ
class LoggingDecorator implements DataFetcher {
constructor(private wrapped: DataFetcher) {}
async fetch(id: string) {
console.time(`fetch:${id}`);
const result = await this.wrapped.fetch(id);
console.timeEnd(`fetch:${id}`);
return result;
}
}
// キャッシュを追加するデコレータ
class CachingDecorator implements DataFetcher {
private cache = new Map<string, unknown>();
constructor(private wrapped: DataFetcher) {}
async fetch(id: string) {
if (this.cache.has(id)) return this.cache.get(id);
const result = await this.wrapped.fetch(id);
this.cache.set(id, result);
return result;
}
}
// デコレータを重ねる(順番が重要)
const fetcher = new LoggingDecorator(
new CachingDecorator(
new UserFetcher()
)
);
await fetcher.fetch("user123"); // ログ → キャッシュ確認 → DB取得
TypeScriptの
@decorator構文もこのパターン。ミドルウェアチェーン(Express・Hono等)も同じ発想。
5. Command パターン(振る舞い)
「操作をオブジェクトとして表現し、Undo・キューイング・ログを可能にする」
// 課題: テキストエディタやゲームで「元に戻す」「やり直す」を実装したい
interface Command {
execute(): void;
undo(): void;
}
// テキストエディタの例
class TextEditor {
text = "";
}
class InsertTextCommand implements Command {
constructor(
private editor: TextEditor,
private text: string,
private position: number
) {}
execute() {
this.editor.text =
this.editor.text.slice(0, this.position) +
this.text +
this.editor.text.slice(this.position);
}
undo() {
this.editor.text =
this.editor.text.slice(0, this.position) +
this.editor.text.slice(this.position + this.text.length);
}
}
// CommandHistory(UndoManager)
class CommandHistory {
private history: Command[] = [];
execute(command: Command) {
command.execute();
this.history.push(command);
}
undo() {
const command = this.history.pop();
command?.undo();
}
}
// 使用例
const editor = new TextEditor();
const history = new CommandHistory();
history.execute(new InsertTextCommand(editor, "Hello", 0));
history.execute(new InsertTextCommand(editor, " World", 5));
console.log(editor.text); // "Hello World"
history.undo();
console.log(editor.text); // "Hello"
実務での使いどころ: Undo/Redo・トランザクションログ・タスクキュー・バッチ処理の遅延実行
まとめ
| パターン | カテゴリ | 解決する問題 |
|---|---|---|
| Strategy | 振る舞い | アルゴリズムの差し替え |
| Observer | 振る舞い | イベント駆動・疎結合 |
| Factory | 生成 | オブジェクト生成の隠蔽 |
| Decorator | 構造 | 機能の動的追加 |
| Command | 振る舞い | 操作のオブジェクト化・Undo |
パターンを知ることで、コードレビューで「これはStrategyパターンで設計できそう」と議論できるようになる。名前がついていると、複雑な設計を1単語で説明できるのが最大の価値。
参考: Refactoring.Guru デザインパターン / Design Patterns: Elements of Reusable Object-Oriented Software