design-patterns について
一般的なソフトウェア設計課題に対する再利用可能な解決策を解説するスキルです。生成、構造、振る舞い、関数型のパターンを理解し、選択やアンチパターンの把握に役立ちます。
主な機能
- 01生成パターンと構造パターン
- 02振る舞いパターンと関数型パターン
- 03パターン選択の指針
- 04代表的なアンチパターンと代替案
活用シーン
- 01オブジェクト生成に使うパターンの選択
- 02互換性のないインターフェースの接続
- 03イベント駆動の振る舞いの構成
- 04コードに見られるアンチパターンの確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ miles990/ claude-software-skills --skill design-patternsインストール後、Claude Code で/design-patternsを入力して読み込まれたか確認します。
詳細
デザインパターン
概要
ソフトウェア設計でよくある問題に対する再利用可能な解決策です。パターンを理解することで、設計アイデアを効果的に伝え、車輪の再発明を避けることができます。
生成に関するパターン
ファクトリメソッド
目的: 正確なクラスを指定せずにオブジェクトを生成する
// Abstract factory
interface PaymentProcessor {
process(amount: number): Promise<Result>;
}
class StripeProcessor implements PaymentProcessor { /* ... */ }
class PayPalProcessor implements PaymentProcessor { /* ... */ }
function createProcessor(type: string): PaymentProcessor {
switch (type) {
case 'stripe': return new StripeProcessor();
case 'paypal': return new PayPalProcessor();
default: throw new Error(`Unknown processor: ${type}`);
}
}
ビルダー
目的: 複雑なオブジェクトを段階的に構築する
class QueryBuilder {
private query: Query = {};
select(...fields: string[]) {
this.query.fields = fields;
return this;
}
from(table: string) {
this.query.table = table;
return this;
}
where(condition: Condition) {
this.query.conditions ??= [];
this.query.conditions.push(condition);
return this;
}
build(): string {
return this.compile(this.query);
}
}
// Usage
const sql = new QueryBuilder()
.select('id', 'name')
.from('users')
.where({ age: { gte: 18 } })
.build();
シングルトン
目的: 単一のインスタンスを保証する(使用は控えめに)
// Modern approach: module-level instance
// database.ts
class Database {
private constructor() {}
private static instance: Database;
static getInstance(): Database {
if (!Database.instance) {
Database.instance = new Database();
}
return Database.instance;
}
}
// Better: Dependency injection
class UserService {
constructor(private db: Database) {}
}
構造に関するパターン
アダプター
目的: 互換性のないインターフェースを連携させる
// Legacy API returns XML
interface LegacyAPI {
getDataXML(): string;
}
// New code expects JSON
interface ModernAPI {
getData(): object;
}
class APIAdapter implements ModernAPI {
constructor(private legacy: LegacyAPI) {}
getData(): object {
const xml = this.legacy.getDataXML();
return this.parseXML(xml);
}
private parseXML(xml: string): object {
// Convert XML to object
}
}
デコレーター
目的: 元のコードを変更せずに振る舞いを追加する
// Base interface
interface DataSource {
read(): string;
write(data: string): void;
}
// Decorator adds encryption
class EncryptedDataSource implements DataSource {
constructor(private wrapped: DataSource) {}
read(): string {
return this.decrypt(this.wrapped.read());
}
write(data: string): void {
this.wrapped.write(this.encrypt(data));
}
}
// Decorator adds compression
class CompressedDataSource implements DataSource {
constructor(private wrapped: DataSource) {}
// Similar pattern...
}
// Compose decorators
const source = new EncryptedDataSource(
new CompressedDataSource(
new FileDataSource('data.txt')
)
);
ファサード
目的: 複雑なサブシステムを簡素化する
// Complex subsystems
class VideoConverter { /* ... */ }
class AudioExtractor { /* ... */ }
class BitrateCalculator { /* ... */ }
class CodecFactory { /* ... */ }
// Simple facade
class VideoExporter {
export(video: Video, format: Format): File {
const codec = CodecFactory.getCodec(format);
const bitrate = BitrateCalculator.optimal(video);
const converter = new VideoConverter(codec, bitrate);
const audio = AudioExtractor.extract(video);
return converter.convert(video, audio);
}
}
// Client only needs facade
const exporter = new VideoExporter();
const mp4 = exporter.export(video, 'mp4');
プロキシ
目的: オブジェクトへのアクセスを制御する
// Virtual proxy for lazy loading
class ImageProxy implements Image {
private realImage: RealImage | null = null;
constructor(private filename: string) {}
display(): void {
if (!this.realImage) {
this.realImage = new RealImage(this.filename); // Expensive
}
this.realImage.display();
}
}
// Protection proxy
class SecureDocumentProxy implements Document {
constructor(
private doc: Document,
private user: User
) {}
read(): string {
if (!this.user.hasPermission('read')) {
throw new Error('Access denied');
}
return this.doc.read();
}
}
振る舞いに関するパターン
オブザーバー
目的: 状態の変化を依存先に通知する
class EventEmitter<T extends Record<string, unknown>> {
private listeners = new Map<keyof T, Set<Function>>();
on<K extends keyof T>(event: K, callback: (data: T[K]) => void) {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.add(callback);
// Return unsubscribe function
return () => this.listeners.get(event)?.delete(callback);
}
emit<K extends keyof T>(event: K, data: T[K]) {
this.listeners.get(event)?.forEach(cb => cb(data));
}
}
// Usage
interface Events {
userCreated: User;
orderPlaced: Order;
}
const emitter = new EventEmitter<Events>();
emitter.on('userCreated', user => sendWelcomeEmail(user));
ストラテジー
目的: 実行時にアルゴリズムを差し替える
interface CompressionStrategy {
compress(data: Buffer): Buffer;
}
class ZipCompression implements CompressionStrategy {
compress(data: Buffer): Buffer { /* ... */ }
}
class GzipCompression implements CompressionStrategy {
compress(data: Buffer): Buffer { /* ... */ }
}
class FileCompressor {
constructor(private strategy: CompressionStrategy) {}
setStrategy(strategy: CompressionStrategy) {
this.strategy = strategy;
}
compress(file: File): Buffer {
return this.strategy.compress(file.data);
}
}
コマンド
目的: アクションをオブジェクトとしてカプセル化する
interface Command {
execute(): void;
undo(): void;
}
class MoveCommand implements Command {
private previousPosition: Position;
constructor(
private object: GameObject,
private newPosition: Position
) {}
execute() {
this.previousPosition = this.object.position;
this.object.position = this.newPosition;
}
undo() {
this.object.position = this.previousPosition;
}
}
class CommandHistory {
private history: Command[] = [];
private current = -1;
execute(command: Command) {
command.execute();
this.history = this.history.slice(0, this.current + 1);
this.history.push(command);
this.current++;
}
undo() {
if (this.current >= 0) {
this.history[this.current].undo();
this.current--;
}
}
redo() {
if (this.current < this.history.length - 1) {
this.current++;
this.history[this.current].execute();
}
}
}
ステート
目的: 状態に応じて振る舞いを変更する
interface State {
handle(context: Context): void;
}
class DraftState implements State {
handle(context: Context) {
console.log('Document is draft');
// Can edit, save, submit for review
}
}
class ReviewState implements State {
handle(context: Context) {
console.log('Document under review');
// Can approve or reject
}
}
class PublishedState implements State {
handle(context: Context) {
console.log('Document is published');
// Read-only, can unpublish
}
}
class Document {
private state: State = new DraftState();
setState(state: State) {
this.state = state;
}
handle() {
this.state.handle(this);
}
}
関数型パターン
モナド(Result/Option)
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };
function map<T, U, E>(
result: Result<T, E>,
fn: (value: T) => U
): Result<U, E> {
return result.ok
? { ok: true, value: fn(result.value) }
: result;
}
function flatMap<T, U, E>(
result: Result<T, E>,
fn: (value: T) => Result<U, E>
): Result<U, E> {
return result.ok ? fn(result.value) : result;
}
// Usage - compose operations safely
const result = pipe(
parseJSON(input),
flatMap(validate),
flatMap(save),
map(formatResponse)
);
パイプライン / ミドルウェア
type Middleware<T> = (context: T, next: () => Promise<void>) => Promise<void>;
class Pipeline<T> {
private middlewares: Middleware<T>[] = [];
use(middleware: Middleware<T>) {
this.middlewares.push(middleware);
return this;
}
async execute(context: T) {
let index = 0;
const next = async (): Promise<void> => {
if (index < this.middlewares.length) {
const middleware = this.middlewares[index++];
await middleware(context, next);
}
};
await next();
}
}
避けるべきアンチパターン
| アンチパターン | 問題 | 解決策 |
|---|---|---|
| 神オブジェクト | 1つのクラスがすべてを行う | 責務を絞ったクラスに分割する |
| スパゲッティコード | 絡み合った依存関係 | SRP を適用し、モジュールを使用する |
| 金のハンマー | すべての箇所で1つのパターンを使う | 問題ごとに適切なパターンを選ぶ |
| 早すぎる最適化 | 計測前に最適化する | まずプロファイリングし、その後で最適化する |
判断ガイド
Need to create objects?
├── Complex construction → Builder
├── Family of related objects → Abstract Factory
├── Subclass decides type → Factory Method
└── Single instance → Singleton (rarely)
Need to structure objects?
├── Make incompatible interfaces work → Adapter
├── Add behavior dynamically → Decorator
├── Simplify complex system → Facade
└── Control access → Proxy
Need to manage behavior?
├── Notify on state change → Observer
├── Swap algorithms → Strategy
├── Encapsulate actions → Command
└── State-dependent behavior → State
関連スキル
- [[architecture-patterns]] - より高レベルなパターン
- [[code-quality]] - パターンを適用するタイミング
- [[refactoring]] - 既存コードへのパターン導入
コメント
Markdown 対応