上一篇:【Go實現】實踐GoF的23種設計模式:備忘錄模式
簡單的分布式應用系統(示例代碼工程):https://github.com/ruanrunxue/Practice-Design-Pattern--Go-Implementation
簡介
適配器模式(Adapter)是最常用的結構型模式之一,在現實生活中,適配器模式也是處處可見,比如電源插頭轉換器,它可以讓英式的插頭工作在中式的插座上。
GoF 對它的定義如下:
Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.
簡單來說,就是適配器模式讓原本因為接口不匹配而無法一起工作的兩個類/結構體能夠一起工作。
適配器模式所做的就是將一個接口Adaptee
,通過適配器Adapter
轉換成 Client 所期望的另一個接口Target
來使用,實現原理也很簡單,就是Adapter
通過實現Target
接口,并在對應的方法中調用Adaptee
的接口實現。

UML 結構

場景上下文
在簡單的分布式應用系統(示例代碼工程)中,db 模塊用來存儲服務注冊信息和系統監控數據,它是一個 key-value 數據庫。在訪問者模式中,我們為它實現了 Table 的按列查詢功能;同時,我們也為它實現了簡單的 SQL 查詢功能(將會在解釋器模式中介紹),查詢的結果是SqlResult
結構體,它提供一個toMap
方法將結果轉換成map
。
為了方便用戶使用,我們將實現在終端控制臺上提供人機交互的能力,如下所示,用戶輸入 SQL 語句,后臺返回查詢結果:

終端控制臺的具體實現為Console
,為了提供可擴展的查詢結果顯示樣式,我們設計了ConsoleRender
接口,但因SqlResult
并未實現該接口,所以Console
無法直接渲染SqlResult
的查詢結果。

為此,我們需要實現一個適配器,讓Console
能夠通過適配器將SqlResult
的查詢結果渲染出來。示例中,我們設計了適配器TableRender
,它實現了ConsoleRender
接口,并以表格的形式渲染出查詢結果,如前文所示。

代碼實現
//demo/db/sql.go
packagedb
//AdapteeSQL語句執行返回的結果,并未實現Target接口
typeSqlResultstruct{
fields[]string
vals[]interface{}
}
func(s*SqlResult)Add(fieldstring,recordinterface{}){
s.fields=append(s.fields,field)
s.vals=append(s.vals,record)
}
func(s*SqlResult)ToMap()map[string]interface{}{
results:=make(map[string]interface{})
fori,f:=ranges.fields{
results[f]=s.vals[i]
}
returnresults
}
//demo/db/console.go
packagedb
//Client終端控制臺
typeConsolestruct{
dbDb
}
//Output調用ConsoleRender完成對查詢結果的渲染輸出
func(c*Console)Output(renderConsoleRender){
fmt.Println(render.Render())
}
//Target接口,控制臺db查詢結果渲染接口
typeConsoleRenderinterface{
Render()string
}
//TableRender表格形式的查詢結果渲染Adapter
//關鍵點1:定義Adapter結構體/類
typeTableRenderstruct{
//關鍵點2:在Adapter中聚合Adaptee,這里是把SqlResult作為TableRender的成員變量
result*SqlResult
}
//關鍵點3:實現Target接口,這里是實現了ConsoleRender接口
func(t*TableRender)Render()string{
//關鍵點4:在Target接口實現中,調用Adaptee的原有方法實現具體的業務邏輯
vals:=t.result.ToMap()
varheader[]string
vardata[]string
forkey,val:=rangevals{
header=append(header,key)
data=append(data,fmt.Sprintf("%v",val))
}
builder:=&strings.Builder{}
table:=tablewriter.NewWriter(builder)
table.SetHeader(header)
table.Append(data)
table.Render()
returnbuilder.String()
}
//這里是另一個Adapter,實現了將error渲染的功能
typeErrorRenderstruct{
errerror
}
func(e*ErrorRender)Render()string{
returne.err.Error()
}
客戶端這么使用:
func(c*Console)Start(){
fmt.Println("welcometoDemoDB,enterexittoend!")
fmt.Println(">pleaseenterasqlexpression:")
fmt.Print(">")
scanner:=bufio.NewScanner(os.Stdin)
forscanner.Scan(){
sql:=scanner.Text()
ifsql=="exit"{
break
}
result,err:=c.db.ExecSql(sql)
iferr==nil{
//關鍵點5:在需要Target接口的地方,傳入適配器Adapter實例,其中創建Adapter實例時需要傳入Adaptee實例
c.Output(NewTableRender(result))
}else{
c.Output(NewErrorRender(err))
}
fmt.Println(">pleaseenterasqlexpression:")
fmt.Print(">")
}
}
在已經有了 Target 接口(ConsoleRender
)和 Adaptee(SqlResult
)的前提下,總結實現適配器模式的幾個關鍵點:
-
定義 Adapter 結構體/類,這里是
TableRender
結構體。 -
在 Adapter 中聚合 Adaptee,這里是把
SqlResult
作為TableRender
的成員變量。 -
Adapter 實現 Target 接口,這里是
TableRender
實現了ConsoleRender
接口。 -
在 Target 接口實現中,調用 Adaptee 的原有方法實現具體的業務邏輯,這里是在
TableRender.Render()
調用SqlResult.ToMap()
方法,得到查詢結果,然后再對結果進行渲染。 -
在 Client 需要 Target 接口的地方,傳入適配器 Adapter 實例,其中創建 Adapter 實例時傳入 Adaptee 實例。這里是在
NewTableRender()
創建TableRender
實例時,傳入SqlResult
作為入參,隨后將TableRender
實例傳入Console.Output()
方法。
擴展
適配器模式在 Gin 中的運用
Gin 是一個高性能的 Web 框架,它的常見用法如下:
//用戶自定義的請求處理函數,類型為gin.HandlerFunc
funcmyGinHandler(c*gin.Context){
...//具體處理請求的邏輯
}
funcmain(){
//創建默認的route引擎,類型為gin.Engine
r:=gin.Default()
//route定義
r.GET("/my-route",myGinHandler)
//route引擎啟動
r.Run()
}
在實際運用場景中,可能存在這種情況。用戶起初的 Web 框架使用了 Go 原生的net/http
,使用場景如下:
//用戶自定義的請求處理函數,類型為http.Handler
funcmyHttpHandler(whttp.ResponseWriter,r*http.Request){
...//具體處理請求的邏輯
}
funcmain(){
//route定義
http.HandleFunc("/my-route",myHttpHandler)
//route啟動
http.ListenAndServe(":8080",nil)
}
因性能問題,當前客戶準備切換至 Gin 框架,顯然,myHttpHandler
因接口不兼容,不能直接注冊到gin.Default()
上。為了方便用戶,Gin 框架提供了一個適配器gin.WrapH
,可以將http.Handler
類型轉換成gin.HandlerFunc
類型,它的定義如下:
//WrapHisahelperfunctionforwrappinghttp.HandlerandreturnsaGinmiddleware.
funcWrapH(hhttp.Handler)HandlerFunc{
returnfunc(c*Context){
h.ServeHTTP(c.Writer,c.Request)
}
}
使用方法如下:
//用戶自定義的請求處理函數,類型為http.Handler
funcmyHttpHandler(whttp.ResponseWriter,r*http.Request){
...//具體處理請求的邏輯
}
funcmain(){
//創建默認的route引擎
r:=gin.Default()
//route定義
r.GET("/my-route",gin.WrapH(myHttpHandler))
//route引擎啟動
r.Run()
}
在這個例子中,gin.Engine
就是 Client,gin.HandlerFunc
是 Target 接口,http.Handler
是 Adaptee,gin.WrapH
是 Adapter。這是一個 Go 風格的適配器模式實現,以更為簡潔的func
替代了struct
。
典型應用場景
- 將一個接口 A 轉換成用戶希望的另外一個接口 B,這樣就能使原來不兼容的接口 A 和接口 B 相互協作。
- 老系統的重構。在不改變原有接口的情況下,讓老接口適配到新的接口。
優缺點
優點
- 能夠使 Adaptee 和 Target 之間解耦。通過引入新的 Adapter 來適配 Target,Adaptee 無須修改,符合開閉原則。
- 靈活性好,能夠很方便地通過不同的適配器來適配不同的接口。
缺點
- 增加代碼復雜度。適配器模式需要新增適配器,如果濫用會導致系統的代碼復雜度增大。
與其他模式的關聯
適配器模式 和裝飾者模式、代理模式在 UML 結構上具有一定的相似性。但適配器模式改變原有對象的接口,但不改變原有功能;而裝飾者模式和代理模式則在不改變接口的情況下,增強原有對象的功能。
文章配圖
可以在用Keynote畫出手繪風格的配圖中找到文章的繪圖方法。
-
模塊
+關注
關注
7文章
2783瀏覽量
49522 -
適配器
+關注
關注
9文章
2024瀏覽量
69289 -
數據庫
+關注
關注
7文章
3900瀏覽量
65753
原文標題:【Go實現】實踐GoF的23種設計模式:適配器模式
文章出處:【微信號:yuanrunzi,微信公眾號:元閏子的邀請】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
適配器模式和代理模式的區別
引適配器模式的作用
適配器模式的本質及分類
java適配器模式實例
JavaScript設計模式之適配器模式

大話設計模式之愛你一萬年:第六章 結構型模式:適配器模式:i7愛妻:為愛找份工作:3.適配器模式之對象適配器

評論