设计模式之桥接模式:如何实现抽象协议与不同实现的绑定?
在上一講中,我們學(xué)習(xí)了第一種結(jié)構(gòu)型模式——適配器模式,它是在不改變目標(biāo)類代碼的情況下,通過(guò)引入適配器類來(lái)給目標(biāo)類擴(kuò)展功能。適配器模式在維護(hù)開發(fā)中經(jīng)常會(huì)使用到,比如,常用在一些無(wú)法直接修改原有功能的舊系統(tǒng)里,開發(fā)一些新的擴(kuò)展功能。
今天,我們繼續(xù)學(xué)習(xí)另外一種結(jié)構(gòu)型模式——橋接模式。橋接模式的原理非常簡(jiǎn)單,但是使用起來(lái)會(huì)有一定的難度,所以相對(duì)于適配器模式來(lái)說(shuō),在理解橋接模式時(shí),我們學(xué)習(xí)的重點(diǎn)要能跳出局部,多從整體結(jié)構(gòu)上去思考。
話不多說(shuō),讓我們正式開始今天的學(xué)習(xí)吧!
模式原理分析
橋接模式的定義是:將抽象部分與它的實(shí)現(xiàn)部分分離,使它們都可以獨(dú)立地變化。
不過(guò),這里的抽象常常容易被理解為抽象類,并將實(shí)現(xiàn)理解為繼承后的“派生類”,但是這樣理解存在局限性,因?yàn)?GoF 的本意是想表達(dá)“從對(duì)象與對(duì)象間的關(guān)系去看,做抽象實(shí)體與抽象行為的分離”,所以使用抽象實(shí)體和抽象行為來(lái)描述更為準(zhǔn)確。
我們來(lái)看看橋接模式的 UML 描述,如下圖所示:
從該圖中,我們可以看到橋接模式主要包含了以下四個(gè)關(guān)鍵角色。
-
抽象實(shí)體:定義的一種抽象分類。比如,電腦中的 CPU、內(nèi)存、攝像頭、顯示屏等。
-
具體實(shí)體:繼承抽象實(shí)體的子類實(shí)體。比如,Intel i7 CPU、三星內(nèi)存、徠卡攝像頭、京東方顯示屏幕等。
-
抽象行為:定義抽象實(shí)體中具備的多種行為。比如,CPU 邏輯運(yùn)算、內(nèi)存讀寫存儲(chǔ)、攝像頭拍照、屏幕顯示圖像等。
-
具體行為:實(shí)現(xiàn)抽象行為的具體算法。比如,Intel 使用 X64 架構(gòu)實(shí)現(xiàn) CPU 邏輯運(yùn)算,Mac M1 芯片使用 ARM 架構(gòu)實(shí)現(xiàn) CPU 邏輯運(yùn)算,等等。
在我看來(lái),橋接模式原理的核心是抽象與抽象之間的分離,這樣分離的好處就在于,具體的實(shí)現(xiàn)類依賴抽象而不是依賴具體,滿足 DIP 原則,很好地完成了對(duì)象結(jié)構(gòu)間的解耦。換句話說(shuō),抽象的分離間接完成了具體類與具體類之間的解耦,它們之間使用抽象來(lái)進(jìn)行組合或聚合,而不再使用繼承。
下面我們?cè)賮?lái)看看橋接模式對(duì)應(yīng) UML 圖的代碼實(shí)現(xiàn),具體如下:
public abstract class AbstractEntity {//行為對(duì)象protected AbstractBehavior myBehavior;//實(shí)體與行為的關(guān)聯(lián)public AbstractEntity(AbstractBehavior aBehavior) {myBehavior = aBehavior;}//子類需要實(shí)現(xiàn)的方法public abstract void request();}public class DetailEntityA extends AbstractEntity {public DetailEntityA(AbstractBehavior aBehavior) {super(aBehavior);}@Overridepublic void request() {super.myBehavior.operation1();}}public class DetailEntityB extends AbstractEntity {public DetailEntityB(AbstractBehavior aBehavior) {super(aBehavior);}@Overridepublic void request() {super.myBehavior.operation2();}}public abstract class AbstractBehavior {public abstract void operation1();public abstract void operation2();}public class DetailBehaviorA extends AbstractBehavior{@Overridepublic void operation1() {System.out.println("op-1 from DetailBehaviorA");}@Overridepublic void operation2() {System.out.println("op-2 from DetailBehaviorA");}}public class DetailBehaviorB extends AbstractBehavior {@Overridepublic void operation1() {System.out.println("op-1 from DetailBehaviorB");}@Overridepublic void operation2() {System.out.println("op-2 from DetailBehaviorB");}}從上面的代碼實(shí)現(xiàn)你會(huì)很容易發(fā)現(xiàn),橋接模式封裝了如下變化:
-
實(shí)體變化;
-
行為變化;
-
兩種變化之間的關(guān)系;
-
變化引起的變化。
橋接模式封裝變化的本質(zhì)上是對(duì)事物進(jìn)行分類(實(shí)體),并對(duì)實(shí)體中的功能性(行為)再劃分的一種解決方案。比如,電子產(chǎn)品可以被分為手機(jī)、電腦等,其中手機(jī)隱藏了手機(jī)一類相關(guān)的變化;同樣,手機(jī)和電腦都具備使用 App 軟件的功能,它們各自隱藏了如何使用 App 的具體方式。在面向?qū)ο筌浖_發(fā)中,我們通常是使用接口或抽象類來(lái)作為抽象實(shí)體和具體實(shí)體,使用具體對(duì)象實(shí)例和實(shí)現(xiàn)接口的對(duì)象作為抽象行為和具體行為。
所以說(shuō),橋接模式的本質(zhì)是通過(guò)對(duì)一個(gè)對(duì)象進(jìn)行實(shí)體與行為的分離,來(lái)將需要使用多層繼承的場(chǎng)景轉(zhuǎn)換為使用組合或聚合的方式,進(jìn)而解耦對(duì)象間的強(qiáng)耦合關(guān)系,達(dá)到對(duì)象與對(duì)象之間的動(dòng)態(tài)綁定的效果,提升代碼結(jié)構(gòu)的擴(kuò)展性。
使用場(chǎng)景分析
一般來(lái)講,橋接模式的常用場(chǎng)景有如下幾種。
-
需要提供平臺(tái)獨(dú)立性的應(yīng)用程序時(shí)。 比如,不同數(shù)據(jù)庫(kù)的 JDBC 驅(qū)動(dòng)程序、硬盤驅(qū)動(dòng)程序等。
-
需要在某種統(tǒng)一協(xié)議下增加更多組件時(shí)。 比如,在支付場(chǎng)景中,我們期望支持微信、支付寶、各大銀行的支付組件等。這里的統(tǒng)一協(xié)議是收款、支付、扣款,而組件就是微信、支付寶等。
-
基于消息驅(qū)動(dòng)的場(chǎng)景。 雖然消息的行為比較統(tǒng)一,主要包括發(fā)送、接收、處理和回執(zhí),但其實(shí)具體客戶端的實(shí)現(xiàn)通常卻各不相同,比如,手機(jī)短信、郵件消息、QQ 消息、微信消息等。
-
拆分復(fù)雜的類對(duì)象時(shí)。 當(dāng)一個(gè)類中包含大量對(duì)象和方法時(shí),既不方便閱讀,也不方便修改。
-
希望從多個(gè)獨(dú)立維度上擴(kuò)展時(shí)。 比如,系統(tǒng)功能性和非功能性角度,業(yè)務(wù)或技術(shù)角度等。
-
需要在運(yùn)行時(shí)切換不同實(shí)現(xiàn)方法時(shí)。 比如,通過(guò)門面模式調(diào)用外部 RPC 服務(wù)。
接下來(lái),我們通過(guò)一個(gè)不同操作系統(tǒng)下的文件上傳例子來(lái)快速理解橋接模式的使用場(chǎng)景。
我們首先創(chuàng)建一個(gè)抽象實(shí)體類 FileUploader,它包含了兩個(gè)抽象行為:上傳(upload)和檢查(check)。
public interface FileUploader {Object upload(String path);boolean check(Object object);}然后,我們?cè)俳⒁粋€(gè)具體實(shí)體類 FileUploaderImpl,其中包含了抽象行為類 FileUploadExcutor(文件上傳執(zhí)行器),實(shí)現(xiàn)了抽象行為 upload 和 check。
public class FileUploaderImpl implements FileUploader {private FileUploadExcutor excutor = null;public FileUploaderImpl(FileUploadExcutor excutor) {this.excutor = excutor;}@Overridepublic Object upload(String path) {return excutor.uploadFile(path);}@Overridepublic boolean check(Object object) {return excutor.checkFile(object);}}public interface FileUploadExcutor {Object uploadFile(String path);boolean checkFile(Object object);}接下來(lái),在 Linux 平臺(tái)上實(shí)現(xiàn)文件上傳執(zhí)行器 LinuxFileUpLoadExcutor,在 Windows 上實(shí)現(xiàn)文件上傳執(zhí)行器 WindowsFileUpLoadExcutor,具體代碼如下所示:
public class LinuxFileUpLoadExcutor implements FileUploadExcutor {@Overridepublic Object uploadFile(String path) {return null;}@Overridepublic boolean checkFile(Object object) {return false;}}public class WindowsFileUpLoadExcutor implements FileUploadExcutor {@Overridepublic Object uploadFile(String path) {return null;}@Overridepublic boolean checkFile(Object object) {return false;}}從上面的代碼中,你會(huì)發(fā)現(xiàn):通過(guò)將文件上傳執(zhí)行器和文件上傳行為進(jìn)行分離,就能實(shí)現(xiàn)實(shí)體和行為的靈活演化。比如,當(dāng)你想要實(shí)現(xiàn)一個(gè)新的上傳到云存儲(chǔ)的文件上傳執(zhí)行器時(shí),你可以先新建一個(gè)叫 OSSFileUploaderImpl 的具體實(shí)現(xiàn)類,然后建立對(duì)應(yīng)的云存儲(chǔ)文件執(zhí)行器,接著再分別實(shí)現(xiàn)華為云、阿里云、騰訊云等各種不同云存儲(chǔ)的文件上傳執(zhí)行器。如果你還想要在執(zhí)行器里加入新的行為,比如刪除,這時(shí)平臺(tái)上的執(zhí)行器并不需要調(diào)用“刪除”這個(gè)接口,這樣就做到了實(shí)體和行為的解耦,極大地提升了代碼的擴(kuò)展性。
細(xì)心的你可能已經(jīng)發(fā)現(xiàn)了,當(dāng)我們做了實(shí)體和行為的分離后,我們還可以結(jié)合更多的模式來(lái)擴(kuò)展橋接模式。比如,這里我簡(jiǎn)單擴(kuò)展了一下橋接模式的 UML 圖:
在實(shí)現(xiàn)抽象行為時(shí),我們可以使用上一講介紹的適配器模式來(lái)擴(kuò)展功能,也可以使用后面會(huì)學(xué)到的門面模式來(lái)擴(kuò)展更多外部的服務(wù)功能。
總體來(lái)說(shuō),橋接模式的使用場(chǎng)景非常靈活,側(cè)重于實(shí)體和行為的分離,然后再基于這兩個(gè)維度進(jìn)行獨(dú)立的演化。
為什么要使用橋接模式?
分析完橋接模式的原理和使用場(chǎng)景后,我們?cè)賮?lái)說(shuō)說(shuō)使用橋接模式的原因,主要有以下三個(gè)。
第一個(gè),為了靈活擴(kuò)展代碼結(jié)構(gòu)。 上面使用了適配器模式和門面模式的橋接模式就是一個(gè)很好的思考方向,與通過(guò)硬編碼直接調(diào)用 API 的形式相比,“通過(guò)模式來(lái)擴(kuò)展”會(huì)更容易控制代碼行數(shù)和邏輯結(jié)構(gòu)。而從我多年的工作經(jīng)驗(yàn)來(lái)看,在很多大規(guī)模的代碼系統(tǒng)中,有結(jié)構(gòu)的代碼的可維護(hù)性會(huì)更好。因?yàn)槭侨藖?lái)維護(hù)代碼的,而人的特性是天生對(duì)結(jié)構(gòu)型的東西更“敏感”,并且靈活的結(jié)構(gòu)在后期進(jìn)行代碼重構(gòu)時(shí)也能更好地替換與修改。
第二個(gè),為了更好地解決跨平臺(tái)兼容性問(wèn)題。 橋接模式之所以能很好地解決跨平臺(tái)的兼容性問(wèn)題,就是因?yàn)闃蚪幽J酵ㄟ^(guò)抽象層次上結(jié)構(gòu)的分離,讓相關(guān)的分類能夠聚合到各自相關(guān)的層次邏輯中,而不同的平臺(tái)對(duì)于同一個(gè) API 在具體的代碼實(shí)現(xiàn)上是不同的,這樣反而符合不同操作系統(tǒng)按照各自維度演化的特性。
第三個(gè),為了在運(yùn)行時(shí)組合不同的組件。 無(wú)論是框架還是外部服務(wù),我們都需要基于一個(gè)統(tǒng)一的協(xié)議進(jìn)行協(xié)同工作,但是通過(guò)靜態(tài)的繼承方法很難做到在程序運(yùn)行時(shí)進(jìn)行方法或組件的動(dòng)態(tài)更換。而使用橋接模式和門面模式就可以很方便地進(jìn)行替換,比如,在上面文件上傳執(zhí)行器的案例中,我們可以使用一個(gè)統(tǒng)一的 API 網(wǎng)關(guān)調(diào)用不同的云服務(wù)來(lái)完成文件上傳。
收益什么?損失什么?
使用橋接模式主要有以下四個(gè)大的優(yōu)點(diǎn)。
-
分離實(shí)體與行為,可以提升各自維度的演化效率。 比如,訂單中的會(huì)員信息可以理解為抽象實(shí)體,普通會(huì)員和 plus 會(huì)員就是不同的具體實(shí)體;會(huì)員中的積分累計(jì)就是抽象行為,不同會(huì)員按照各自的積分計(jì)算軌跡進(jìn)行計(jì)算就是具體行為的體現(xiàn)。那么,會(huì)員可以再繼續(xù)增加更多會(huì)員類別,而積分計(jì)算規(guī)則也可以不斷更新。
-
符合開閉原則,提升代碼復(fù)用性。 每一個(gè)維度的類都以組合或聚合關(guān)系進(jìn)行合作,新增類或修改類都在各自類內(nèi)部進(jìn)行,不影響其他類。
-
用組合關(guān)系替代了多重繼承,提升了代碼結(jié)構(gòu)的演化靈活性。我們都知道多繼承違背了單一職責(zé)原則,雖然關(guān)聯(lián)性更強(qiáng),但復(fù)用性很差。組合關(guān)系的優(yōu)勢(shì)就在于可以在任意階段進(jìn)行升級(jí)與替換,并且可以按需進(jìn)行組合與撤銷,這對(duì)于需求快速變化的開發(fā)場(chǎng)景而言很適用,能夠極大地提升代碼結(jié)構(gòu)的靈活性。
-
符合表達(dá)原則,提升代碼的可理解性。由于橋接模式從抽象層次就進(jìn)行了分離,不同的類別會(huì)按照各自的特點(diǎn)進(jìn)行演化,所以不管是在結(jié)構(gòu)上還是代碼內(nèi)在含義上,都更聚焦,這樣在閱讀代碼時(shí)也就能更容易理解。
同樣,橋接模式也有一些缺點(diǎn)。
-
增加了維護(hù)成本。 橋接模式因?yàn)樾枰龊芏鄬?shí)體和行為的分離,所以會(huì)間接地要增加不少代碼行數(shù)。再加上使用組合和聚合關(guān)系不像繼承關(guān)系那樣容易找到對(duì)象之間的調(diào)用關(guān)系,稍不注意就會(huì)影響到其他對(duì)象,這樣大大增加了代碼修改維護(hù)的成本。
-
導(dǎo)致性能下降。 組合或聚合關(guān)系在面向?qū)ο缶幊讨惺褂玫氖俏械膶?shí)現(xiàn)方式,簡(jiǎn)單理解就是調(diào)用的對(duì)象變多了,自然也就影響到了程序的性能。
-
增加設(shè)計(jì)難度。 橋接模式更重視聚合而非繼承關(guān)系,那么就需要建立更多的抽象層,要求開發(fā)者針對(duì)抽象層進(jìn)行設(shè)計(jì)與編程。我們都知道,找到正確的抽象層有時(shí)是一件相當(dāng)困難的事情,雖然現(xiàn)在有很多優(yōu)秀的設(shè)計(jì)能夠作為借鑒,但在一些新的領(lǐng)域里依然會(huì)有一定的設(shè)計(jì)難度。
總結(jié)
橋接模式可以說(shuō)是 DIP 原則的具體實(shí)踐。在軟件開發(fā)中,一個(gè)對(duì)象可以從實(shí)體和行為兩個(gè)角度來(lái)進(jìn)行分離,其實(shí)就是將依賴從一個(gè)大而全的對(duì)象變換到依賴兩個(gè)可以獨(dú)立變化的維度,控制也就發(fā)生了反轉(zhuǎn)。
橋接模式因?yàn)橹匾暯M合和聚合,從而有效避免了多重繼承帶來(lái)的問(wèn)題。也就是說(shuō),通過(guò)抽象實(shí)體與抽象行為的關(guān)聯(lián),將靜態(tài)的繼承關(guān)系轉(zhuǎn)換為了動(dòng)態(tài)的組合關(guān)系,從而使得系統(tǒng)結(jié)構(gòu)更加靈活。
在實(shí)際開發(fā)中,你應(yīng)該將橋接模式和更多的模式結(jié)合起來(lái)使用,將不同模式或服務(wù)作為某一個(gè)獨(dú)立的維度來(lái)進(jìn)行演化。另外,多在實(shí)踐中尋找可以做實(shí)體和行為分離的場(chǎng)景,并嘗試使用橋接模式來(lái)解決,這才是學(xué)習(xí)橋接模式最好的辦法。
課后思考
在使用橋接模式新增加實(shí)體時(shí),你認(rèn)為能夠復(fù)用現(xiàn)有抽象行為的可能性有多大?如果不能,那又會(huì)帶來(lái)哪些問(wèn)題呢?
歡迎留言分享,我會(huì)第一時(shí)間給你回復(fù)。
在下一講,我會(huì)接著與你分享“組合模式:如何用樹形結(jié)構(gòu)處理對(duì)象之間的復(fù)雜關(guān)系?”這個(gè)話題,記得按時(shí)來(lái)聽課!
總結(jié)
以上是生活随笔為你收集整理的设计模式之桥接模式:如何实现抽象协议与不同实现的绑定?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 记在2019,winter is com
- 下一篇: .NET周刊【1月第1期 2023-01