アダプターパターン
互換性のないインターフェースを変換し、既存のコードを変更せずに連携させるデザインパターン
設計パターンオブジェクト指向
アダプターパターンとは
アダプターパターンは、互換性のないインターフェースを変換し、既存のコードを変更せずに連携させるデザインパターンである。GoF デザインパターンの構造パターンに分類される。電源プラグの変換アダプターと同じ概念。
問題: インターフェースの不一致
// 既存のコード: Logger インターフェースを期待
interface Logger {
info(message: string): void;
error(message: string): void;
}
// 外部ライブラリ: 異なるインターフェース
class ExternalLogger {
log(level: string, msg: string) { /* ... */ }
}
// → Logger インターフェースと互換性がない
アダプターで解決
class ExternalLoggerAdapter implements Logger {
constructor(private external: ExternalLogger) {}
info(message: string) { this.external.log('INFO', message); }
error(message: string) { this.external.log('ERROR', message); }
}
// 既存のコードを変更せずに外部ライブラリを使える
const logger: Logger = new ExternalLoggerAdapter(new ExternalLogger());
logger.info('Hello'); // ExternalLogger.log('INFO', 'Hello') が呼ばれる
AWS SDK のアダプター
// DynamoDB のインターフェースを抽象化
interface Repository<T> {
get(id: string): Promise<T | null>;
put(item: T): Promise<void>;
}
// DynamoDB アダプター
class DynamoDBRepository<T> implements Repository<T> {
constructor(private db: DynamoDBClient, private tableName: string) {}
async get(id: string): Promise<T | null> {
const result = await this.db.send(new GetItemCommand({
TableName: this.tableName, Key: { id: { S: id } },
}));
return result.Item ? unmarshall(result.Item) as T : null;
}
async put(item: T): Promise<void> {
await this.db.send(new PutItemCommand({
TableName: this.tableName, Item: marshall(item),
}));
}
}
// テスト用のインメモリアダプター
class InMemoryRepository<T> implements Repository<T> {
private store = new Map<string, T>();
async get(id: string) { return this.store.get(id) ?? null; }
async put(item: T) { this.store.set((item as any).id, item); }
}
アダプターのメリット
既存コードを変更せずに新しいインターフェースに適合させられる (開放閉鎖原則)。テスト時にモックアダプターに差し替えが容易で、外部 SDK の変更が内部コードに波及するのを防げる。
いつ使うか
| ケース | 推奨 |
|---|---|
| 外部ライブラリのインターフェースが合わない | ✅ |
| テスト用にモックを差し替えたい | ✅ |
| DB を将来変更する可能性がある | ✅ |
| 内部コード同士の連携 | ❌ インターフェースを統一する方が良い |
アダプターパターンの背景や設計思想は関連書籍に詳しい。