💠
【デザインパターン】Adapter
アダプター
アダプターっていうのは、あるクラスのインターフェース(API)を、そのクラスを使う側が欲しい別のインターフェースに変換するパターンやねん。アダプターパターンは、互換性のないクラスを使わなあかん場合に効果的やねん。
実際の所、コンセントをイメージしたらわかりやすいと思うで。コンセントは国やタイプによっていろんな種類があるやろ?でも、アダプターを使えば、形状が異なるコンセントの違いを気にせずに家電を使えるんやで。
つまり、利用する側と利用される側の違いを埋める役割を果たすわけや。
アダプターパターンの構成要素
アダプターパターンには4つの構成要素があるねん。
- クライアント:あるクラスの機能を使う側のクラス
- ターゲット:クライアントが必要とする機能のAPIを定義するインターフェース
- アダプター:アダプティを継承する子クラスで、ターゲットのAPIを実装して、アダプティの機能をクライアントが使えるようにする
- アダプティ:利用される側のクラスで、クライアントと互換性がない
アダプターパターンを採用することのメリット
クライアントからはターゲットインターフェースしか見えなくなることで、アダプターの詳細を知らなくても使うことができるようになるんやで。
つまり、クライアントとアダプターの依存関係を切ることができ、アダプターの切り替えや修正が簡単になるメリットがあるねん。
また、変換のためのコードをビジネスロジックから分離できるから単一責任の原則に違反しない形になり、責務が明確になるねん。インターフェースを介することでオープンクローズドの原則にも沿う形になるで。
アダプターの実装例
実際にある例を見てみよう。
クライアントがデータを取得したいとしたら、CSV形式でデータを取得したいと思うやろ?でも、返却されるデータ形式はJson形式なんや。そんな時、アダプターを実装することでクライアントがデータを取得するためのインターフェースを提供できるようになるねん。
継承を利用した実装
export {}
type JsonData = {
[key: string]: string;
}
type Target = {
getCsvData(): string;
}
// アダプティ
class Library {
getJsonData(): JsonData[] {
return [
{
"data1": "dataA",
"data2": "dataB",
},
{
"data1": "dataC",
"data2": "dataD",
}
];
}
}
// アダプター
class JsonToCsvAdapter implements Target {
constructor(private adaptee: Library) { }
getCsvData(): string {
const json = this.adaptee.getJsonData();
const header = Object.keys(json[0]).join(',') + '\\n';
const formatHeader = header.replace(/\\n/g, '')
const body = json.map(v => {
return Object.keys(v).map(key => v[key]).join(',');
}).join('\\n');
return `${formatHeader} : ${body}`;
}
}
const adaptee = new Library();
const adapter = new JsonToCsvAdapter(adaptee);
console.log(adapter.getCsvData()); // data1,data2 : dataA,dataB\ndataC,dataD
いかがでしょうか?
アダプターがデータ形式の変換の役割を担ってくれてるな。
Json形式のデータを当てはめたらCSV形式で返してくれる責務を持ったアダプターの完成やで。
Discussion