デザインパターンをTypeScriptで学ぶ（Adapter）

デザインパターンと言えば [TECHSCORE](https://www.techscore.com/tech/DesignPattern/index.html/) さんの記事が有名ですね。時々「このコード、何かのパターンで表現できそう...」と思うのですが、日常業務でデザインパターンをサッとかっこよく使う事ができていません。という訳で TECHSCORE さんの記事を読みつつ、自分の中で噛み砕いた知識を TypeScript のコードで書き起こしてみようと思います。

- 対象読者：Java のコードを読むのに苦戦する人（自分！！）
- 環境：typescript 4.0

23 パターン全部まとめたら記事同士のリンクを貼ろうと思います。

# Adapterパターン

サードパーティの API を使用した時、そのバージョンによるインターフェースの差分を吸収する例です。Adaptor パターンには **委譲** と **継承** の 2 つの構文があります。

## 委譲

```TypeScript
/*
Target
*/
interface ThirdPartyApi {
  refreshToken(token: string): string;
}

/*
Adapter（委譲）
*/
class ThirdPartyApiImpl implements ThirdPartyApi {
  private api = new ApiForV1();

  refreshToken(token: string): string {
    // 変数として持たせた別クラスに処理を委譲
    return this.api.refreshToken(token);
  }
}

/*
Adaptee
*/
class ApiForV1 implements ThirdPartyApi {
  sdk = new SdkForV1();

  refreshToken(token: string): string {
    return this.sdk.getTokenInfo(token).refresh().token;
  }
}

class ApiForV2 implements ThirdPartyApi {
  sdk = new SdkForV2();

  refreshToken(token: string): string {
    return this.sdk.login(token).generateNewToken();
  }
}
```

## 継承

```TypeScript
/*
Target
*/
interface ThirdPartyApi {
  refreshToken(token: string): string;
}

/*
Adapter（継承）
*/
class ThirdPartyApiImpl extends ApiForV1 implements ThirdPartyApi {
  refreshToken(token: string): string {
    // 継承元のクラスのメソッドを呼び出す
    return super.refreshToken(token);
  }
}

/*
Adaptee
*/
class ApiForV1 implements ThirdPartyApi {
  sdk = new SdkForV1();

  refreshToken(token: string): string {
    return this.sdk.getTokenInfo(token).refresh().token;
  }
}

class ApiForV2 implements ThirdPartyApi {
  sdk = new SdkForV2();

  refreshToken(token: string): string {
    return this.sdk.login(token).generateNewToken();
  }
}
```

WebAPI は認証トークンをリクエストに含めるものとして、SDK は認証トークンを更新するメソッドを備えています。

# 説明

## Target

アプリから利用する SDK の機能をインターフェースとして切り出します。

```TypeScript
/*
Target
*/
interface ThirdPartyApi {
  refreshToken(token: string): string;
}
```

## Adaptee

インターフェースの振る舞いを実装したクラスです。SDK のバージョンアップによりオブジェクトやメソッドのインターフェースがガラリと変わるという事があるため、バージョンごとにクラスを分けています。

```TypeScript
/*
Adaptee
*/
class ApiForV1 implements ThirdPartyApi {
  sdk = new SdkForV1();

  refreshToken(token: string): string {
    return this.sdk.getTokenInfo(token).refresh().token;
  }
}

class ApiForV2 implements ThirdPartyApi {
  sdk = new SdkForV2();

  refreshToken(token: string): string {
    return this.sdk.login(token).generateNewToken();
  }
}
```

## Adatpter

**Adaptee** と同じくインターフェースの振る舞いを実装したクラスですが、こちらはアプリから利用するためのものです。このクラスが `V1` `V2` どちらの API を使うのかを知っていて、委譲または継承により機能を呼び出します。

```TypeScript
/*
Adapter（委譲）
*/
class ThirdPartyApiImpl implements ThirdPartyApi {
  private api = new ApiForV1();

  refreshToken(token: string): string {
    return this.api.refreshToken(token);
  }
}
```

```TypeScript
/*
Adapter（継承）
*/
class ThirdPartyApiImpl extends ApiForV1 implements ThirdPartyApi {
  refreshToken(token: string): string {
    return super.refreshToken(token);
  }
}
```

## 利用例

アプリは **Adapter** を利用しているだけで `V1` `V2`どちらの SDK を使っているのか知る必要がなくなります。

```TypeScript
const thirdPartyApi = new ThirdPartyApiImpl();

const deadToken = "...";
const reviveToken = thirdPartyApi.refreshToken(legacyToken);
```

SDK のバージョンを切り替える時は **Adapter** を変更します。

```diff
class ThirdPartyApiImpl implements ThirdPartyApi {
-  private api = new ApiForV1();
+  private api = new ApiForV2();
```

```diff
- class ThirdPartyApiImpl extends ApiForV1 implements ThirdPartyApi {
+ class ThirdPartyApiImpl extends ApiForV2 implements ThirdPartyApi {
```

# このパターンが解決する問題

もしアプリから SDK のメソッドを直接呼び出した場合、API とそれに伴う SDK のバージョンアップによりコードの書き換えが大量に発生します。メソッドの引数の順番が `login(token:string, option:string)` から `login(option:string, token:string)` に変わったとしたら、コンパイルエラーやユニットテストを手がかりにコードを修正する事はできません。

**Adapter** はこのような互換性のないインターフェースの相違を安全に「適合」させる事ができるパターンです。