アダプターパターン

互換性のないインターフェースを変換し、既存のコードを変更せずに連携させるデザインパターン

設計パターンオブジェクト指向

アダプターパターンとは

アダプターパターンは、互換性のないインターフェースを変換し、既存のコードを変更せずに連携させるデザインパターンである。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 を将来変更する可能性がある
内部コード同士の連携 ❌ インターフェースを統一する方が良い

アダプターパターンの背景や設計思想は関連書籍に詳しい。

関連用語