精通 Golang 介面卡模式

KaoLengMian發表於2022-10-22

原文首發於我的部落格:kaolengmian7.com/posts/adapter.htm...

示例程式碼已經上傳到我的 Github:github.com/kaolengmian7/golang-dem...,可以把程式碼拉下來跑一跑~

核心思想

介面卡是兩個不相容的介面之間的橋樑,使介面不相容的物件能夠相互合作。

使用場景

有動機地修改一個正常執行的系統的介面,這時應該考慮使用介面卡模式。
1. 封裝有缺陷的介面
2. 封裝多個介面
3. 替換依賴
4. 相容老版本介面
5. 適配不同格式資料

一定要注意:介面卡為的是解決正在服役的專案的問題,而不是在設計階段新增的。如果在設計階段就考慮使用介面卡模式,那這個設計一定不合格。

實戰

假如你正在開發一款股票市場監測程式, 它會從不同來源下載 XML 格式的股票資料, 然後向使用者呈現出美觀的圖表。

type XmlHandler interface {
 ProcessXml(xml string)}

type xmlHandler struct{}

func (x *xmlHandler) ProcessXml(xml string) {
 log.Printf("xml:%s processing", xml)}

在開發過程中, 你決定在程式中整合一個第三方智慧分析函式庫。 但是遇到了一個問題, 那就是分析函式庫只相容 JSON 格式的資料。

// 分析庫介面
type JsonHandler interface {
 ProcessJson(json []byte)}

type jsonHandler struct {
}

func (j *jsonHandler) ProcessJson(json []byte) {
 log.Printf("json processing")}

image.png
你可以修改函式庫來支援 XML。 但是, 這可能需要大量程式碼。 或者,我們可以將 XML 轉換成 JSON?

xml 與 json 代表了現實中不相容的兩個介面。
為了演示方便,我用string型別表示xml檔案,用[]byte表示json檔案

介面卡就是為兩個相容兩個介面而生,在此例中:xmlAdapter繼承了XmlHandler介面,其中封裝了對介面的處理。結果就是,客戶端的 JSON 處理被“適配”成 XML 處理。

type xmlAdapter struct {
 JsonHandler}

func (x *xmlAdapter) ProcessXml(xml string) {
 // 模擬物件轉換:xml -> json
 json := []byte(xml) // 處理 json 檔案
 x.ProcessJson(json)}

func TestAdapter(t *testing.T) {
 inputJson := []byte("json_file") // 原客戶端
 handler := &jsonHandler{} handler.ProcessJson(inputJson)
 // 客戶端接入介面卡,利用 原有的 JsonHandler 處理 xml 檔案。
 inputXml := "xml_file" adapter := &xmlAdapter{&jsonHandler{}} adapter.ProcessXml(inputXml)}

優點

靈活性強,上線新功能不需要改動大量原始碼,僅需要在介面層做一層型別轉換。

缺點

過多地使用介面卡,會讓系統非常零亂。比如,表明上看到呼叫的是 A 介面,其實內部被適配成了 B 介面的實現。
一個系統如果太多出現這種情況,無異於一場災難。因此如果不是很有必要,儘量不使用介面卡,而是直接對系統進行重構。

更進一步:加強版介面卡

對於 Golang 這種可以實現多繼承的語言來講,介面卡有更優雅的解決方案:讓介面卡實現所有介面。

type adapter struct {
 jsonHandler JsonHandler xmlHandler  XmlHandler}

這樣的介面卡強大的多,想用誰的實現就用誰的實現,與此同時完全規避了上面提到的缺點,不容易產生誤解。

func TestAdapterInGolang(t *testing.T) {
 inputJson := []byte("json_file") inputXml := "xml_file" // 原客戶端
 handler := &jsonHandler{} handler.ProcessJson(inputJson)
 // 客戶端接入介面卡
 adapter := &adapter{&jsonHandler{}, &xmlHandler{}} adapter.jsonHandler.ProcessJson(inputJson) // 想用 json 用 json adapter.xmlHandler.ProcessXml(inputXml)    // 想用 xml 用 xml}

也可以重寫函式:

type adapter struct {}

func (x *adapter) ProcessJson(json []byte) {
 // 自定義處理邏輯
}

func (x *adapter) ProcessXml(xml string) {
 // 自定義處理邏輯
}

func TestAdapterInGolang(t *testing.T) {
 inputJson := []byte("json_file") inputXml := "xml_file" // 原客戶端
 handler := &jsonHandler{} handler.ProcessJson(inputJson)
 // 客戶端接入介面卡
 adapter := &adapter{&jsonHandler{}, &xmlHandler{}} adapter.ProcessJson(inputJson) // 想用 json 用 json adapter.ProcessXml(inputXml)    // 想用 xml 用 xml}

參考:

  1. refactoringguru.cn/design-patterns...
  2. shusheng007.top/2021/09/08/018/
  3. www.runoob.com/design-pattern/adap...
  4. lailin.xyz/post/adapter.html#comme...
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章