Design Pattern - Adapter Pattern (橋接器模式)

Posted by Ryan Tseng on 2019-09-20

Adapter Pattern (橋接器模式)

Adapter 模式實際上是一種結構型的模式,讓不相容的物件彼此能夠進行溝通。

試想你正在建立一個期貨市場的監看應用程式,這個應用程式會從各種不同的資料來源取得期貨的資料,讓這些資料可以透過漂亮的圖表呈現在使用者面前。

在某些原因下,你決定透過第三方的 Library 來精進這個應用程式,不過有個但書,這個三方 Library 只能支援 JSON 格式的資料。

你可以透過加入一些程式碼讓他可以相容於 XML 格式,但是你可能會修改到你自己相依在這個 Library 相關的程式碼,更差的情況底下你甚至會需要下載三方 Library 來修改他的原始碼。

Show me the CODE!!!

以下因為偷懶貪圖方便所以使用 Typescript 做為範例的程式碼,但我想應該跟 C# 差不多啦,所以各位看文章的朋友們如果有看不懂的再告知我。

Sample Code (Object Adapter)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
export class ThirdPartyLib {
getFinancialData(): any {
console.log('get Financial Data from remote sever. And response XML');
}
getAccountInfo(): any {
console.log('get AccountInfo from remote sever. And response XML');
}
}

// methods in this spec will response JSON
export interface ISpec {
fetchFinancialInfo(): any;
fetchUserInfo(): any;
}

export class MyLib implements ISpec {
constructor(private svc: ThirdPartyLib) {}
fetchFinancialInfo(): any {
const result = this.svc.getFinancialData();
console.log('svc.getFinancialData() (XML to JSON)');
return result;
}
fetchUserInfo(): any {
const result = this.svc.getAccountInfo();
console.log('svc.getAccountInfo() (XML to JSON)');
return result;
}
}

const thirdPartyLib = new ThirdPartyLib();
const adapter = new MyLib(thirdPartyLib);

adapter.fetchFinancialInfo();
adapter.fetchUserInfo();

以下是上面範例所執行的結果,可以看到在執行原本不符合規格的 Service 之後,我們還會去處理 XML to JSON 的部分,讓他符合我們的預期。

程式碼解析 (Object Adapter)

  • ISpec
1
2
3
4
export interface ISpec {
fetchFinancialInfo(): any;
fetchUserInfo(): any;
}
  • Adaptee
1
2
3
4
5
6
7
8
9
10
// 這就當作上面提到的那個第三方的 Library 裡面定義的 class
// 所以他目前的規格會回傳 XML,但是我們內部預期他要能夠回傳 JSON
export class ThirdPartyLib {
getFinancialData(): any {
console.log('get Financial Data from remote sever. And response XML');
}
getAccountInfo(): any {
console.log('get AccountInfo from remote sever. And response XML');
}
}
  • Adapter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export class MyLib implements ISpec {
constructor(private svc: ThirdPartyLib) {}
fetchFinancialInfo(): any {
const result = this.svc.getFinancialData();
// 我們還是會呼叫原本的 service,只不過中間會墊一層轉換
// 這邊透過 console.log 來示意
console.log('svc.getFinancialData() (XML to JSON)');
return result;
}
fetchUserInfo(): any {
const result = this.svc.getAccountInfo();
// 我們還是會呼叫原本的 service,只不過中間會墊一層轉換
// 這邊透過 console.log 來示意
console.log('svc.getAccountInfo() (XML to JSON)');
return result;
}
}

Sample Code (Class Adapter)

關於這個就沒有特別的範例,因為這類型的 Adapter 需要能夠支援多重繼承的語言 (例如:C++),但概念上我認為差不多,只是在這種的 Adapter 裡面,會直接繼承第三方的 Library,實作起來大概會像這樣

(Typescript 沒辦法多重繼承,所以實際上下面的語法沒辦法執行,所以只是示意 Class Adapter 的概念而已)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
export class ThirdPartyLib {
getFinancialData(): any {
console.log('get FinancialData from remote sever. And response XML');
}
getAccountInfo(): any {
console.log('get AccountInfo from remote sever. And response XML');
}
}

export class MyLib {
fetchFinancialInfo(): any {
console.log('get FinancialInfo from remote sever. And expected response format is JSON');
}
fetchUserInfo(): any {
console.log('get UserInfo from remote sever. And expected response format is JSON');
}

}

export class MyAdapter extends MyLib, ThirdPartyLib {
financialData(): any {
const result = this.getFinancialData();
console.log('XML to JSON');
return result;
}

userInfo(): any {
const result = this.fetchUserInfo();
console.log('XML to JSON');
return result;
}
}

結論

其實很多朋友應該都會遇到使用外部 Library 結果跟自己規格不符的時候,我想這個時候就很適合透過這種方式來讓紊亂的規格能夠在你自己的實作上能夠稍微比較統一一點,實務上我想這算是一個很好理解而且出現頻率也頗高的一種 Pattern,或許你也有這樣寫過,只是你不知道而已。

今天這篇文章就介紹到這裡,感謝大家的收看。

參考資料