3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

デザインパターンをTypeScriptで学ぶ(Adapter)

Posted at

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

  • 対象読者:Java のコードを読むのに苦戦する人(自分!!)
  • 環境:typescript 4.0

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

Adapterパターン

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

委譲

/*
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();
  }
}

継承

/*
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 の機能をインターフェースとして切り出します。

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

Adaptee

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

/*
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 を使うのかを知っていて、委譲または継承により機能を呼び出します。

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

  refreshToken(token: string): string {
    return this.api.refreshToken(token);
  }
}
/*
Adapter(継承)
*/
class ThirdPartyApiImpl extends ApiForV1 implements ThirdPartyApi {
  refreshToken(token: string): string {
    return super.refreshToken(token);
  }
}

利用例

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

const thirdPartyApi = new ThirdPartyApiImpl();

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

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

class ThirdPartyApiImpl implements ThirdPartyApi {
-  private api = new ApiForV1();
+  private api = new ApiForV2();
- 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 はこのような互換性のないインターフェースの相違を安全に「適合」させる事ができるパターンです。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?