アダプターパターン
互換性のないインターフェースを変換し、既存のコードを変更せずに連携させるデザインパターン
設計パターンオブジェクト指向
アダプターパターンとは
アダプターパターンは、互換性のないインターフェースを変換し、既存のコードを変更せずに連携させるデザインパターンである。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 のアダプター
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 の変更が内部コードに波及するのを防げる。
いつ使うか
アダプターを挟むかどうかは、インターフェースの不一致を自分たちで直せるかどうかで決まる。
| 場面 | 判断 | 理由 |
|---|---|---|
| 外部ライブラリや SDK のインターフェースが合わない | 使う | 相手側は変更できないため、変換処理をアダプター 1 箇所に閉じ込める |
| テストで本物の依存を差し替えたい | 使う | 呼び出し側を変えずに、同じインターフェースの別実装 (インメモリ実装など) に置き換えられる |
| DB や外部サービスを将来入れ替える可能性がある | 使う | 呼び出し側は Repository のような抽象だけに依存し、入れ替えの影響がアダプター内に留まる |
| 自分たちが書いた内部コード同士の連携 | 使わない | 双方を直せるので、インターフェースを統一する方が層が増えず読みやすい |
アダプターパターンの背景や設計思想は関連書籍に詳しい。
この記事は役に立ちましたか?
関連用語
デザインパターン
ソフトウェア設計で繰り返し現れる問題に対する再利用可能な解決策のカタログ
ファクトリパターン
オブジェクトの生成ロジックをカプセル化し、生成方法の詳細を隠蔽するデザインパターン
コンポジション
オブジェクトを組み合わせて機能を構築する設計手法で、継承よりも柔軟性が高い
インターフェース分離の原則
SOLID の I - クライアントが使わないメソッドへの依存を強制しない原則
ポートとアダプター
アプリケーションのコアロジックを外部技術から分離し、ポート (インターフェース) とアダプター (実装) で接続するアーキテクチャ
ポリモーフィズム
同じインターフェースで異なる型を扱い、呼び出し側を変えずに型ごとの処理へ振り分ける仕組み