0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

實(shí)踐GoF的23種設(shè)計(jì)模式實(shí)現(xiàn):橋接模式

元閏子的邀請(qǐng) ? 來(lái)源:元閏子的邀請(qǐng) ? 2024-04-14 09:30 ? 次閱讀

簡(jiǎn)介

GoF 對(duì)橋接模式(Bridge Pattern)的定義如下:

Decouple an abstraction from its implementation so that the two can vary independently.

也即,將抽象部分和實(shí)現(xiàn)部分進(jìn)行解耦,使得它們能夠各自往獨(dú)立的方向變化。

橋接模式解決了在模塊有多種變化方向的情況下,用繼承所導(dǎo)致的類爆炸問(wèn)題。

舉個(gè)例子,一個(gè)產(chǎn)品有形狀和顏色兩個(gè)特征(變化方向),其中形狀分為方形和圓形,顏色分為紅色和藍(lán)色。如果采用繼承的設(shè)計(jì)方案,那么就需要新增4個(gè)產(chǎn)品子類:方形紅色、圓形紅色、方形藍(lán)色、圓形紅色。如果形狀總共有 m 種變化,顏色有 n 種變化,那么就需要新增 m * n 個(gè)產(chǎn)品子類!

現(xiàn)在我們使用橋接模式進(jìn)行優(yōu)化,將形狀和顏色分別設(shè)計(jì)為抽象接口獨(dú)立出來(lái),這樣需要新增 2 個(gè)形狀子類:方形和圓形,以及 2 個(gè)顏色子類:紅色和藍(lán)色。同樣,如果形狀總共有 m 種變化,顏色有 n 種變化,總共只需要新增 m + n 個(gè)子類!

1f8ae0a2-f948-11ee-a297-92fbcf53809c.png

上述例子中,我們通過(guò)將形狀和顏色抽象為一個(gè)接口,使產(chǎn)品不再依賴于具體的形狀和顏色細(xì)節(jié),從而達(dá)到了解耦的目的。橋接模式本質(zhì)上就是面向接口編程,可以給系統(tǒng)帶來(lái)很好的靈活性和可擴(kuò)展性。如果一個(gè)對(duì)象存在多個(gè)變化的方向,而且每個(gè)變化方向都需要擴(kuò)展,那么使用橋接模式進(jìn)行設(shè)計(jì)那是再合適不過(guò)了。

當(dāng)然,Go 語(yǔ)言從語(yǔ)言特性本身就把繼承剔除,但橋接模式中分離變化、面向接口編程的思想仍然值得學(xué)習(xí)。

UML 結(jié)構(gòu)

1fb4db3c-f948-11ee-a297-92fbcf53809c.png

場(chǎng)景上下文

在簡(jiǎn)單的分布式應(yīng)用系統(tǒng)(示例代碼工程)中,我們?cè)O(shè)計(jì)了一個(gè) Monitor 監(jiān)控系統(tǒng)模塊,它可以看成是一個(gè)簡(jiǎn)單的 ETL 系統(tǒng),負(fù)責(zé)對(duì)監(jiān)控?cái)?shù)據(jù)進(jìn)行采集、處理、輸出。監(jiān)控?cái)?shù)據(jù)來(lái)源于在線商場(chǎng)服務(wù)集群各個(gè)服務(wù),當(dāng)前通過(guò)消息隊(duì)列模塊 Mq 傳遞到監(jiān)控系統(tǒng),經(jīng)處理后,存儲(chǔ)到數(shù)據(jù)庫(kù)模塊 Db 上。

1fe1c4f8-f948-11ee-a297-92fbcf53809c.jpg

我們假設(shè)未來(lái)要上線一個(gè)不支持對(duì)接消息隊(duì)列的服務(wù)、結(jié)果數(shù)據(jù)也需要存儲(chǔ)到 ClickHouse 以供后續(xù)分析,為了應(yīng)對(duì)未來(lái)多變的需求,我們有必要將監(jiān)控系統(tǒng)設(shè)計(jì)得足夠的可擴(kuò)展。

于是,整個(gè)模塊被設(shè)計(jì)為插件化風(fēng)格的架構(gòu),Pipeline是數(shù)據(jù)處理的流水線,其中包含了Input、Filter和Output三類插件,Input負(fù)責(zé)從各類數(shù)據(jù)源中獲取監(jiān)控?cái)?shù)據(jù),F(xiàn)ilter負(fù)責(zé)數(shù)據(jù)處理,Output負(fù)責(zé)將處理后的數(shù)據(jù)輸出。

1fee1bc2-f948-11ee-a297-92fbcf53809c.png

上述設(shè)計(jì)中,我們抽象出Input、Filter和Output三類插件,它們各種往獨(dú)立的方向變化,最后在Pipeline上進(jìn)行靈活組合,這使用橋接模式正合適。

20109e22-f948-11ee-a297-92fbcf53809c.png

代碼實(shí)現(xiàn)

//關(guān)鍵點(diǎn)1:明確產(chǎn)品的變化點(diǎn),這里是input、filter和output三類插件,它們各自變化

//demo/monitor/input/input_plugin.go
packageinput

//關(guān)鍵點(diǎn)2:將產(chǎn)品的變化點(diǎn)抽象成接口,這里是input.Plugin,filter.Plugin和output.Plugin
//Plugin輸入插件
typePlugininterface{
plugin.Plugin
Input()(*plugin.Event,error)
}

//關(guān)鍵點(diǎn)3:實(shí)現(xiàn)產(chǎn)品變化點(diǎn)的接口,這里是SocketInput,AddTimestampFilter和MemoryDbOutput
//demo/monitor/input/socket_input.go
typeSocketInputstruct{
socketnetwork.Socket
endpointnetwork.Endpoint
packetschan*network.Packet
isUninstalluint32
}

func(s*SocketInput)Input()(*plugin.Event,error){
packet,ok:=<-s.packets
????if?!ok?{
????????return?nil,?plugin.ErrPluginUninstalled
????}
????event?:=?plugin.NewEvent(packet.Payload())
????event.AddHeader("peer",?packet.Src().String())
????return?event,?nil
}

//?demo/monitor/filter/filter_plugin.go
package?filter

//?Plugin?過(guò)濾插件
type?Plugin?interface?{
????plugin.Plugin
????Filter(event?*plugin.Event)?*plugin.Event
}

//?demo/monitor/filter/add_timestamp_filter.go
//?AddTimestampFilter?為MonitorRecord增加時(shí)間戳
type?AddTimestampFilter?struct?{
}

func?(a?*AddTimestampFilter)?Filter(event?*plugin.Event)?*plugin.Event?{
????re,?ok?:=?event.Payload().(*model.MonitorRecord)
????if?!ok?{
????????return?event
????}
????re.Timestamp?=?time.Now().Unix()
????return?plugin.NewEvent(re)
}


//?demo/monitor/output/output_plugin.go
//?Plugin?輸出插件
type?Plugin?interface?{
????plugin.Plugin
????Output(event?*plugin.Event)?error
}

//?demo/monitor/output/memory_db_output.go
type?MemoryDbOutput?struct?{
????db????????db.Db
????tableName?string
}

func?(m?*MemoryDbOutput)?Output(event?*plugin.Event)?error?{
????r,?ok?:=?event.Payload().(*model.MonitorRecord)
????if?!ok?{
????return?fmt.Errorf("memory?db?output?unknown?event?type?%T",?event.Payload())
????}
????return?m.db.Insert(m.tableName,?r.Id,?r)
}

//?關(guān)鍵點(diǎn)4:定義產(chǎn)品的接口或者實(shí)現(xiàn),通過(guò)組合的方式把變化點(diǎn)橋接起來(lái)。
//?demo/monitor/pipeline/pipeline_plugin.go
//?Plugin?pipeline由input、filter、output三種插件組成,定義了一個(gè)數(shù)據(jù)處理流程
//?數(shù)據(jù)流向?yàn)?input?->filter->output
//如果是接口,可以通過(guò)定義Setter方法達(dá)到聚合的目的。
typePlugininterface{
plugin.Plugin
SetInput(inputinput.Plugin)
SetFilter(filterfilter.Plugin)
SetOutput(outputoutput.Plugin)
}

//如果是結(jié)構(gòu)體,直接把變化點(diǎn)作為成員變量來(lái)達(dá)到聚合的目的。
typepipelineTemplatestruct{
inputinput.Plugin
filterfilter.Plugin
outputoutput.Plugin
isCloseuint32
runfunc()
}

func(p*pipelineTemplate)SetInput(inputinput.Plugin){
p.input=input
}

func(p*pipelineTemplate)SetFilter(filterfilter.Plugin){
p.filter=filter
}

func(p*pipelineTemplate)SetOutput(outputoutput.Plugin){
p.output=output
}

//demo/monitor/pipeline/simple_pipeline.go
//SimplePipeline簡(jiǎn)單Pipeline實(shí)現(xiàn),每次運(yùn)行時(shí)新啟一個(gè)goroutine
typeSimplePipelinestruct{
pipelineTemplate
}

在本系統(tǒng)中,我們通過(guò)配置文件來(lái)靈活組合插件,利用反射來(lái)實(shí)現(xiàn)插件的實(shí)例化,實(shí)例化的實(shí)現(xiàn)使用了抽象工廠模式,詳細(xì)的實(shí)現(xiàn)方法可參考【Go實(shí)現(xiàn)】實(shí)踐GoF的23種設(shè)計(jì)模式:抽象工廠模式。

總結(jié)實(shí)現(xiàn)橋接模式的幾個(gè)關(guān)鍵點(diǎn):

明確產(chǎn)品的變化點(diǎn),這里是 input、filter 和 output 三類插件,它們各自變化。

將產(chǎn)品的變化點(diǎn)抽象成接口,這里是input.Plugin,filter.Plugin和output.Plugin。

實(shí)現(xiàn)產(chǎn)品變化點(diǎn)的接口,這里是SocketInput,AddTimestampFilter和MemoryDbOutput。

定義產(chǎn)品的接口或者實(shí)現(xiàn),通過(guò)組合的方式把變化點(diǎn)橋接起來(lái)。這里是pipeline.Plugin通過(guò)Setter方法將input.Plugin,filter.Plugin和output.Plugin三個(gè)抽象接口橋接了起來(lái)。后面即可實(shí)現(xiàn)各類 input、filter 和 output 的靈活組合了。

擴(kuò)展

TiDB 中的橋接模式

TiDB是一款出色的分布式關(guān)系型數(shù)據(jù)庫(kù),它對(duì)外提供了一套插件框架,方便用戶進(jìn)行功能擴(kuò)展。TiDB 的插件框架的設(shè)計(jì),也運(yùn)用到了橋接模式的思想。

203ccb32-f948-11ee-a297-92fbcf53809c.png

如上圖所示,每個(gè)Plugin都包含Validate、OnInit、OnShutdown、OnFlush四個(gè)待用戶實(shí)現(xiàn)的接口,它們可以按照各自的方向去變化,然后靈活組合在Plugin中。

//PluginpresentsaTiDBplugin.
typePluginstruct{
*Manifest
library*gplugin.Plugin
Pathstring
Disableduint32
StateState
}

//Manifestdescribesplugininfoandhowitcandobypluginitself.
typeManifeststruct{
Namestring
Descriptionstring
RequireVersionmap[string]uint16
Licensestring
BuildTimestring
//Validatedefinesthevalidatelogicforplugin.
//returnserrorwillstoploadpluginprocessandTiDBstartup.
Validatefunc(ctxcontext.Context,manifest*Manifest)error
//OnInitdefinestheplugininitlogic.
//itwillbecalledafterdomaininit.
//returnerrorwillstoploadpluginprocessandTiDBstartup.
OnInitfunc(ctxcontext.Context,manifest*Manifest)error
//OnShutDowndefinestheplugincleanuplogic.
//returnerrorwillwritelogandcontinueshutdown.
OnShutdownfunc(ctxcontext.Context,manifest*Manifest)error
//OnFlushdefinesflushlogicafterexecuted`flushtidbplugins`.
//itwillbecalledafterOnInit.
//returnerrorwillwritelogandcontinuewatchfollowingflush.
OnFlushfunc(ctxcontext.Context,manifest*Manifest)error
flushWatcher*flushWatcher

Versionuint16
KindKind
}

TiDB 在實(shí)現(xiàn)插件框架時(shí),使用函數(shù)式編程的方式來(lái)定義 OnXXX 接口,更具有 Go 風(fēng)格。

典型應(yīng)用場(chǎng)景

從多個(gè)維度上對(duì)系統(tǒng)/類/結(jié)構(gòu)體進(jìn)行擴(kuò)展,如插件化架構(gòu)。

在運(yùn)行時(shí)切換不同的實(shí)現(xiàn),如插件化架構(gòu)。

用于構(gòu)建與平臺(tái)無(wú)關(guān)的程序適配層。

優(yōu)缺點(diǎn)

優(yōu)點(diǎn)

可實(shí)現(xiàn)抽象不分與實(shí)現(xiàn)解耦,變化實(shí)現(xiàn)時(shí),客戶端無(wú)須修改代碼,符合開閉原則。

每個(gè)分離的變化點(diǎn)都可以專注于自身的演進(jìn),符合單一職責(zé)原則。

缺點(diǎn)

過(guò)度的抽象(過(guò)度設(shè)計(jì))會(huì)使得接口膨脹,導(dǎo)致系統(tǒng)復(fù)雜性變大,難以維護(hù)。

與其他模式的關(guān)聯(lián)

橋接模式通常與抽象工廠模式搭配使用,比如,在本文例子中,可以通過(guò)抽象工廠模式對(duì)各個(gè) Plugin 完成實(shí)例化,詳情見(jiàn)【Go實(shí)現(xiàn)】實(shí)踐GoF的23種設(shè)計(jì)模式:抽象工廠模式。

文章配圖

可以在用Keynote畫出手繪風(fēng)格的配圖中找到文章的繪圖方法。



審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • UML
    UML
    +關(guān)注

    關(guān)注

    0

    文章

    122

    瀏覽量

    30836
  • 數(shù)據(jù)處理
    +關(guān)注

    關(guān)注

    0

    文章

    554

    瀏覽量

    28483
  • go語(yǔ)言
    +關(guān)注

    關(guān)注

    1

    文章

    156

    瀏覽量

    9004

原文標(biāo)題:【Go實(shí)現(xiàn)】實(shí)踐GoF的23種設(shè)計(jì)模式:橋接模式

文章出處:【微信號(hào):yuanrunzi,微信公眾號(hào):元閏子的邀請(qǐng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    關(guān)于模式遇到的問(wèn)題

    VMware使用模式,已經(jīng)手動(dòng)設(shè)置ip地址和主機(jī)ip同一網(wǎng)段,但是ifconfig顯示的ip地址卻和vmnet8(NAT)同一網(wǎng)段?怎么解決?
    發(fā)表于 10-16 15:06

    CentOS靜態(tài)IP配置(模式

    [2018-08-26]-[CentOS]CentOS靜態(tài)IP配置(模式
    發(fā)表于 05-12 08:27

    23基本的設(shè)計(jì)模式總結(jié)

    一樣。?提到設(shè)計(jì)模式,不得不感謝GoF(***,四人組),他們1995年出版的《設(shè)計(jì)模式》一書,第一次將設(shè)計(jì)模式提升到理論高度,并將之規(guī)范化。書中一共總結(jié)了
    發(fā)表于 03-01 06:08

    C#23設(shè)計(jì)模式【完整】

    C#23設(shè)計(jì)模式
    發(fā)表于 08-21 17:38 ?69次下載

    23java設(shè)計(jì)模式

    JAVA的設(shè)計(jì)模式經(jīng)前人總結(jié)可以分為23 設(shè)計(jì)模式根據(jù)使用類型可以分為三: 1、創(chuàng)建模式
    發(fā)表于 09-23 15:17 ?1次下載

    實(shí)踐GoF23設(shè)計(jì)模式:命令模式簡(jiǎn)介

    因此,我們需要對(duì)請(qǐng)求進(jìn)行抽象,將上下文信息封裝到請(qǐng)求對(duì)象里,這其實(shí)就是命令模式,而該請(qǐng)求對(duì)象就是 Command。
    的頭像 發(fā)表于 01-13 16:36 ?701次閱讀

    模式的目標(biāo)與設(shè)計(jì)

    模式的目標(biāo)是使對(duì)象的抽象部分與實(shí)現(xiàn)部分分離,使之可以分別獨(dú)立變化,以盡量避免產(chǎn)生耦合。
    的頭像 發(fā)表于 06-01 14:29 ?477次閱讀
    <b class='flag-5'>橋</b><b class='flag-5'>接</b><b class='flag-5'>模式</b>的目標(biāo)與設(shè)計(jì)

    設(shè)計(jì)模式結(jié)構(gòu)性:模式

    模式不是將兩個(gè)不相干的類鏈接,而是將一個(gè)需要多維度變化的類拆分成抽象部分和實(shí)現(xiàn)部分,并且在抽象層對(duì)兩者做組合關(guān)聯(lián),是用組合的方式來(lái)解決繼承的問(wèn)題。
    的頭像 發(fā)表于 06-08 10:49 ?721次閱讀
    設(shè)計(jì)<b class='flag-5'>模式</b>結(jié)構(gòu)性:<b class='flag-5'>橋</b><b class='flag-5'>接</b><b class='flag-5'>模式</b>

    遠(yuǎn)程網(wǎng)關(guān)模式實(shí)現(xiàn)同一局域網(wǎng)組網(wǎng)管理(Superlink)

    助虛擬網(wǎng)卡技術(shù),先由服務(wù)器平臺(tái)下發(fā)網(wǎng)絡(luò)參數(shù),然后將各ZP網(wǎng)關(guān)及下掛用戶終端直接建立在同一個(gè)局域網(wǎng)通道之內(nèi),實(shí)現(xiàn)各網(wǎng)絡(luò)節(jié)點(diǎn)設(shè)備可以自由
    的頭像 發(fā)表于 04-26 15:32 ?1001次閱讀
    遠(yuǎn)程網(wǎng)關(guān)<b class='flag-5'>橋</b><b class='flag-5'>接</b><b class='flag-5'>模式</b><b class='flag-5'>實(shí)現(xiàn)</b>同一局域網(wǎng)組網(wǎng)管理(Superlink)

    模式應(yīng)用場(chǎng)景

    模式(Bridge Pattern):將抽象和實(shí)現(xiàn)解耦, 使得兩者可以獨(dú)立地變化。 另外一解釋是:一個(gè)類存在兩個(gè)(或多個(gè))獨(dú)立變化的
    的頭像 發(fā)表于 10-09 14:30 ?1024次閱讀
    <b class='flag-5'>橋</b><b class='flag-5'>接</b><b class='flag-5'>模式</b>應(yīng)用場(chǎng)景

    實(shí)踐GoF23設(shè)計(jì)模式:備忘錄模式

    相對(duì)于代理模式、工廠模式等設(shè)計(jì)模式,備忘錄模式(Memento)在我們?nèi)粘i_發(fā)中出鏡率并不高,除了應(yīng)用場(chǎng)景的限制之外,另一個(gè)原因,可能是備忘錄模式
    的頭像 發(fā)表于 11-25 09:05 ?487次閱讀
    <b class='flag-5'>實(shí)踐</b><b class='flag-5'>GoF</b>的<b class='flag-5'>23</b><b class='flag-5'>種</b>設(shè)計(jì)<b class='flag-5'>模式</b>:備忘錄<b class='flag-5'>模式</b>

    實(shí)踐GoF23設(shè)計(jì)模式:適配器模式

    適配器模式所做的就是將一個(gè)接口 Adaptee,通過(guò)適配器 Adapter 轉(zhuǎn)換成 Client 所期望的另一個(gè)接口 Target 來(lái)使用,實(shí)現(xiàn)原理也很簡(jiǎn)單,就是 Adapter 通過(guò)實(shí)現(xiàn) Target接口,并在對(duì)應(yīng)的方法中調(diào)用
    的頭像 發(fā)表于 12-10 14:00 ?443次閱讀
    <b class='flag-5'>實(shí)踐</b><b class='flag-5'>GoF</b>的<b class='flag-5'>23</b><b class='flag-5'>種</b>設(shè)計(jì)<b class='flag-5'>模式</b>:適配器<b class='flag-5'>模式</b>

    VMware虛擬機(jī)的三網(wǎng)絡(luò)模式

    。VMware提供了三網(wǎng)絡(luò)模式模式、NAT模式和主機(jī)
    的頭像 發(fā)表于 02-04 11:17 ?1731次閱讀

    實(shí)踐GoF23設(shè)計(jì)模式:解釋器模式

    解釋器模式(Interpreter Pattern)應(yīng)該是 GoF23 設(shè)計(jì)模式中使用頻率最少的一
    的頭像 發(fā)表于 04-01 11:01 ?554次閱讀
    <b class='flag-5'>實(shí)踐</b><b class='flag-5'>GoF</b>的<b class='flag-5'>23</b><b class='flag-5'>種</b>設(shè)計(jì)<b class='flag-5'>模式</b>:解釋器<b class='flag-5'>模式</b>

    網(wǎng)絡(luò)模式是什么? 網(wǎng)絡(luò)模式和路由模式的區(qū)別

    網(wǎng)絡(luò)模式是一網(wǎng)絡(luò)連接方式,它可以將多個(gè)設(shè)備連接在一起,使它們可以相互通信。在網(wǎng)絡(luò)
    的頭像 發(fā)表于 05-10 13:48 ?3636次閱讀