08.橋接模式Bridge

1.初識橋接模式

將抽象部分與它的實(shí)現(xiàn)部分分離梅惯,使它們都可以獨(dú)立地變化簸搞。

  • Abstraction:抽象部分的接口。通常在這個(gè)對象里面塞栅,要維護(hù)一個(gè)實(shí)現(xiàn)部分的對象引用当船,在抽象對象里面的方法,需要調(diào)用實(shí)現(xiàn)部分的對象來完成徙歼。這個(gè)對象里面的方法则酝,通常都是跟具體的業(yè)務(wù)相關(guān)的方法。
    RefinedAbstraction:擴(kuò)展抽象部分的接口庄敛,通常在這些對象里面俗壹,定義跟實(shí)際業(yè)務(wù)相關(guān)的方法,這些方法的實(shí)現(xiàn)通常會(huì)使用Abstraction中定義的方法藻烤,也可能需要調(diào)用實(shí)現(xiàn)部分的對象來完成绷雏。
    Implementor:定義實(shí)現(xiàn)部分的接口,這個(gè)接口不用和Abstraction里面的方法一致怖亭,通常是由Implementor接口提供基本的操作涎显,而Abstraction里面定義的是基于這些基本操作的業(yè)務(wù)方法,也就是說Abstraction定義了基于這些基本操作的較高層次的操作兴猩。
    ConcreteImplementor:真正實(shí)現(xiàn)Implementor接口的對象期吓。

2.體會(huì)橋接模式

2.1 場景問題——發(fā)送提示消息

考慮這樣一個(gè)實(shí)際的業(yè)務(wù)功能:發(fā)送提示消息∏阒ィ基本上所有帶業(yè)務(wù)流程處理的系統(tǒng)都會(huì)有這樣的功能讨勤,比如某人有新的工作了,需要發(fā)送一條消息提示他晨另。

從業(yè)務(wù)上看潭千,消息又分成普通消息、加急消息和特急消息多種借尿,不同的消息類型刨晴,業(yè)務(wù)功能處理是不一樣的屉来,比如加急消息是在消息上添加加急,而特急消息除了添加特急外狈癞,還會(huì)做一條催促的記錄茄靠,多久不完成會(huì)繼續(xù)催促。從發(fā)送消息的手段上看亿驾,又有系統(tǒng)內(nèi)短消息嘹黔、手機(jī)短消息、郵件等等莫瞬。

現(xiàn)在要實(shí)現(xiàn)這樣的發(fā)送提示消息的功能儡蔓,該如何實(shí)現(xiàn)呢?

2.2 不用模式的解決方案

2.2.1 實(shí)現(xiàn)簡化版本

先考慮實(shí)現(xiàn)一個(gè)簡單點(diǎn)的版本疼邀,比如:消息先只是實(shí)現(xiàn)發(fā)送普通消息喂江,發(fā)送的方式呢,先實(shí)現(xiàn)系統(tǒng)內(nèi)短消息和郵件旁振。其它的功能获询,等這個(gè)版本完成過后,再繼續(xù)添加拐袜,這樣先把問題簡單化吉嚣,實(shí)現(xiàn)起來會(huì)容易一點(diǎn)。

(1)由于發(fā)送普通消息會(huì)有兩種不同的實(shí)現(xiàn)方式蹬铺,為了讓外部能統(tǒng)一操作尝哆,因此,把消息設(shè)計(jì)成接口甜攀,然后由兩個(gè)不同的實(shí)現(xiàn)類秋泄,分別實(shí)現(xiàn)系統(tǒng)內(nèi)短消息方式和郵件發(fā)送消息的方式。此時(shí)系統(tǒng)結(jié)構(gòu)如圖


2.2.2 實(shí)現(xiàn)發(fā)送加急消息

添加發(fā)送加急消息的功能规阀,同樣是站內(nèi)短消息和 Email的方式恒序。

加急消息的實(shí)現(xiàn)跟普通消息不同,加急消息會(huì)自動(dòng)在消息上添加加急谁撼,然后再發(fā)送消息歧胁;另外加急消息會(huì)提供監(jiān)控的方法,讓客戶端可以隨時(shí)通過這個(gè)方法來了解對于加急消息處理的進(jìn)度等厉碟。因此加急消息需要擴(kuò)展出一個(gè)新的接口喊巍,除了基本的發(fā)送消息的功能,還需要添加監(jiān)控的功能墨榄,這個(gè)時(shí)候玄糟,系統(tǒng)的結(jié)構(gòu)如圖:


2.2.3 有何問題

上面這樣實(shí)現(xiàn)勿她,好像也能滿足基本的功能要求袄秩,可是這么實(shí)現(xiàn)好不好呢?有沒有什么問題呢?

咱們繼續(xù)向下來添加功能實(shí)現(xiàn)之剧,為了簡潔郭卫,就不再去進(jìn)行代碼示意了,通過實(shí)現(xiàn)的結(jié)構(gòu)示意圖就可以看出實(shí)現(xiàn)上的問題背稼。

1.繼續(xù)添加特急消息的處理
特急消息不需要查看處理進(jìn)程贰军,只要沒有完成,就直接催促蟹肘,也就是說词疼,對于特急消息,在普通消息的處理基礎(chǔ)上帘腹,需要添加催促的功能贰盗。而特急消息、還有催促的發(fā)送方式阳欲,相應(yīng)的實(shí)現(xiàn)方式還是發(fā)送站內(nèi)短消息和Email兩種舵盈,此時(shí)系統(tǒng)的結(jié)構(gòu)如圖:


仔細(xì)觀察上面的系統(tǒng)結(jié)構(gòu)示意圖,會(huì)發(fā)現(xiàn)一個(gè)很明顯的問題球化,那就是:通
過這種繼承的方式來擴(kuò)展消息處理秽晚,會(huì)非常不方便。

你看筒愚,實(shí)現(xiàn)加急消息處理的時(shí)候赴蝇,必須實(shí)現(xiàn)站內(nèi)短消息和Email兩種處理方式,因?yàn)闃I(yè)務(wù)處理可能不同锨能;在實(shí)現(xiàn)特急消息處理的時(shí)候扯再,又必須實(shí)現(xiàn)站內(nèi)短消息和Email這兩種處理方式。

這意味著址遇,以后每次擴(kuò)展一下消息處理熄阻,都必須要實(shí)現(xiàn)這兩種處理方式,是不是很痛苦倔约,這還不算完秃殉,如果要添加新的實(shí)現(xiàn)方式呢?繼續(xù)向下看吧浸剩。

2.繼續(xù)添加發(fā)送手機(jī)消息的處理方式
如果看到上面的實(shí)現(xiàn)钾军,你還感覺問題不是很大的話,繼續(xù)完成功能绢要,添加發(fā)送手機(jī)消息的處理方式吏恭。

仔細(xì)觀察現(xiàn)在的實(shí)現(xiàn),如果要添加一種新的發(fā)送消息的方式重罪,是需要在每一種抽象的具體實(shí)現(xiàn)里面樱哼,都要添加發(fā)送手機(jī)消息的處理的哀九。也就是說:發(fā)送普通消息、加急消息和特急消息的處理搅幅,都可以通過手機(jī)來發(fā)送阅束。這就意味著,需要添加三個(gè)實(shí)現(xiàn)茄唐。此時(shí)系統(tǒng)結(jié)構(gòu)如圖:


3.小結(jié)一下出現(xiàn)的問題
采用通過繼承來擴(kuò)展的實(shí)現(xiàn)方式息裸,有個(gè)明顯的缺點(diǎn):擴(kuò)展消息的種類不太容易,不同種類的消息具有不同的業(yè)務(wù)沪编,也就是有不同的實(shí)現(xiàn)呼盆,在這種情況下,每個(gè)種類的消息蚁廓,需要實(shí)現(xiàn)所有不同的消息發(fā)送方式宿亡。

更可怕的是,如果要新加入一種消息的發(fā)送方式纳令,那么會(huì)要求所有的消息種類挽荠,都要加入這種新的發(fā)送方式的實(shí)現(xiàn)。

要是考慮業(yè)務(wù)功能上再擴(kuò)展一下呢平绩?比如:要求實(shí)現(xiàn)群發(fā)消息圈匆,也就是一次可以發(fā)送多條消息,這就意味著很多地方都得修改捏雌,太恐怖了跃赚。
那么究竟該如何實(shí)現(xiàn)才能既實(shí)現(xiàn)功能,又能靈活的擴(kuò)展呢性湿?

2.3 使用模式的解決方案

2.3.1 使用模式來解決的思路

仔細(xì)分析上面的示例纬傲,根據(jù)示例的功能要求,示例的變化具有兩個(gè)緯度肤频,一個(gè)緯度是抽象的消息這邊叹括,包括普通消息、加急消息和特急消息宵荒,這幾個(gè)抽象的消息本身就具有一定的關(guān)系汁雷,加急消息和特急消息會(huì)擴(kuò)展普通消息;另一個(gè)緯度在具體的消息發(fā)送方式上报咳,包括站內(nèi)短消息侠讯、Email和手機(jī)短信息,這幾個(gè)方式是平等的暑刃,可被切換的方式厢漩。這兩個(gè)緯度一共可以組合出9種不同的可能性來,它們的關(guān)系如下圖


現(xiàn)在出現(xiàn)問題的根本原因岩臣,就在于消息的抽象和實(shí)現(xiàn)是混雜在一起的溜嗜,這就導(dǎo)致了柴底,一個(gè)緯度的變化,會(huì)引起另一個(gè)緯度進(jìn)行相應(yīng)的變化粱胜,從而使得程序擴(kuò)展起來非常困難。

要想解決這個(gè)問題狐树,就必須把這兩個(gè)緯度分開焙压,也就是將抽象部分和實(shí)現(xiàn)部分分開,讓它們相互獨(dú)立抑钟,這樣就可以實(shí)現(xiàn)獨(dú)立的變化涯曲,使擴(kuò)展變得簡單。

橋接模式通過引入實(shí)現(xiàn)的接口在塔,把實(shí)現(xiàn)部分從系統(tǒng)中分離出去幻件;那么,抽象這邊如何使用具體的實(shí)現(xiàn)呢蛔溃?肯定是面向?qū)崿F(xiàn)的接口來編程了绰沥,為了讓抽象這邊能夠很方便的與實(shí)現(xiàn)結(jié)合起來,把頂層的抽象接口改成抽象類贺待,在里面持有一個(gè)具體的實(shí)現(xiàn)部分的實(shí)例徽曲。

這樣一來,對于需要發(fā)送消息的客戶端而言麸塞,就只需要?jiǎng)?chuàng)建相應(yīng)的消息對象秃臣,然后調(diào)用這個(gè)消息對象的方法就可以了,這個(gè)消息對象會(huì)調(diào)用持有的真正的消息發(fā)送方式來把消息發(fā)送出去哪工。也就是說客戶端只是想要發(fā)送消息而已奥此,并不想關(guān)心具體如何發(fā)送。

2.3.2 使用模式來解決

首要任務(wù)是把抽象部分和實(shí)現(xiàn)部分分離出來雁比,分析要實(shí)現(xiàn)的功能稚虎,抽象部分就是各個(gè)消息的類型所對應(yīng)的功能,而實(shí)現(xiàn)部分就是各種發(fā)送消息的方式偎捎。

其次要給抽象部分和實(shí)現(xiàn)部分分別定義接口祥绞,然后分別實(shí)現(xiàn)它們。

1.從簡單功能開始
從相對簡單的功能開始鸭限,先實(shí)現(xiàn)普通消息和加急消息的功能蜕径,發(fā)送方式先實(shí)現(xiàn)站內(nèi)短消息和Email這兩種。使用橋接模式來實(shí)現(xiàn)這些功能的程序結(jié)構(gòu)如圖


2.添加功能
看了上面的實(shí)現(xiàn)败京,發(fā)現(xiàn)使用橋接模式來實(shí)現(xiàn)也不是很困難啊兜喻,關(guān)鍵得看是否能解決前面提出的問題,那就來添加還未實(shí)現(xiàn)的功能看看赡麦,添加對特急消息的處理朴皆,同時(shí)添加一個(gè)使用手機(jī)發(fā)送消息的方式帕识。該怎么實(shí)現(xiàn)呢?

很簡單遂铡,只需要在抽象部分再添加一個(gè)特急消息的類肮疗,擴(kuò)展抽象消息就可以把特急消息的處理功能加入到系統(tǒng)中了;對于添加手機(jī)發(fā)送消息的方式也很簡單扒接,在實(shí)現(xiàn)部分新增加一個(gè)實(shí)現(xiàn)類伪货,實(shí)現(xiàn)用手機(jī)發(fā)送消息的方式,也就可以了钾怔。

這么簡單碱呼?好像看起來完全沒有了前面所提到的問題。的確如此宗侦,采用橋接模式來實(shí)現(xiàn)過后愚臀,抽象部分和實(shí)現(xiàn)部分分離開了,可以相互獨(dú)立的變化矾利,而不會(huì)相互影響姑裂。因此在抽象部分添加新的消息處理,對發(fā)送消息的實(shí)現(xiàn)部分是沒有影響的男旗;反過來增加發(fā)送消息的方式炭分,對消息處理部分也是沒有影響的。

3.理解橋接模式

3.1 認(rèn)識橋接模式

3.1.1:什么是橋接

在橋接模式里面剑肯,不太好理解的就是橋接的概念捧毛,什么是橋接?為何需要橋接让网?如何橋接呀忧?把這些問題搞清楚了,也就基本明白橋接的含義了溃睹。

一個(gè)一個(gè)來而账,先看什么是橋接?所謂橋接因篇,通俗點(diǎn)說就是在不同的東西之間搭一個(gè)橋泞辐,讓他們能夠連接起來,可以相互通訊和使用竞滓。那么在橋接模式中到底是給什么東西來搭橋呢咐吼?就是為被分離了的抽象部分和實(shí)現(xiàn)部分來搭橋,比如前面示例中抽象的消息和具體消息發(fā)送之間搭個(gè)橋商佑。

但是這里要注意一個(gè)問題:在橋接模式中的橋接是單向的锯茄,也就是只能是抽象部分的對象去使用具體實(shí)現(xiàn)部分的對象,而不能反過來,也就是個(gè)單向橋肌幽。

3.1.2:為何需要橋接

為了達(dá)到讓抽象部分和實(shí)現(xiàn)部分都可以獨(dú)立變化的目的晚碾,在橋接模式中,是把抽象部分和實(shí)現(xiàn)部分分離開來的喂急,雖然從程序結(jié)構(gòu)上是分開了格嘁,但是在抽象部分實(shí)現(xiàn)的時(shí)候,還是需要使用具體的實(shí)現(xiàn)的廊移,這可怎么辦呢糕簿?抽象部分如何才能調(diào)用到具體實(shí)現(xiàn)部分的功能呢?很簡單画机,搭個(gè)橋不就可以了,搭個(gè)橋新症,讓抽象部分通過這個(gè)橋就可以調(diào)用到實(shí)現(xiàn)部分的功能了步氏,因此需要橋接。

3.1.3:如何橋接

這個(gè)理解上也很簡單徒爹,只要讓抽象部分擁有實(shí)現(xiàn)部分的接口對象荚醒,這就橋接上了,在抽象部分就可以通過這個(gè)接口來調(diào)用具體實(shí)現(xiàn)部分的功能隆嗅。也就是說界阁,橋接在程序上就體現(xiàn)成了在抽象部分擁有實(shí)現(xiàn)部分的接口對象,維護(hù)橋接就是維護(hù)這個(gè)關(guān)系胖喳。

3.1.4:獨(dú)立變化

橋接模式的意圖:使得抽象和實(shí)現(xiàn)可以獨(dú)立變化泡躯,都可以分別擴(kuò)充。也就是說抽象部分和實(shí)現(xiàn)部分是一種非常松散的關(guān)系丽焊,從某個(gè)角度來講较剃,抽象部分和實(shí)現(xiàn)部分是可以完全分開的,獨(dú)立的技健,抽象部分不過是一個(gè)使用實(shí)現(xiàn)部分對外接口的程序罷了写穴。

如果這么看橋接模式的話,就類似于策略模式了雌贱,抽象部分需要根據(jù)某個(gè)策略啊送,來選擇真實(shí)的實(shí)現(xiàn),也就是說橋接模式的抽象部分相當(dāng)于策略模式的上下文欣孤。更原始的就直接類似于面向接口編程馋没,通過接口分離的兩個(gè)部分而已。但是別忘了降传,橋接模式的抽象部分披泪,是可以繼續(xù)擴(kuò)展和變化的,而策略模式只有上下文搬瑰,是不存在所謂抽象部分的款票。

那抽象和實(shí)現(xiàn)為何還要組合在一起呢控硼?原因是在抽象部分和實(shí)現(xiàn)部分還是存在內(nèi)部聯(lián)系的,抽象部分的實(shí)現(xiàn)通常是需要調(diào)用實(shí)現(xiàn)部分的功能來實(shí)現(xiàn)的

3.1.5:動(dòng)態(tài)變換功能

由于橋接模式中的抽象部分和實(shí)現(xiàn)部分是完全分離的艾少,因此可以在運(yùn)行時(shí)動(dòng)態(tài)組合具體的真實(shí)實(shí)現(xiàn)卡乾,從而達(dá)到動(dòng)態(tài)變換功能的目的。

從另外一個(gè)角度看缚够,抽象部分和實(shí)現(xiàn)部分沒有固定的綁定關(guān)系了幔妨,因此同一個(gè)真實(shí)實(shí)現(xiàn)可以被不同的抽象對象使用,反過來谍椅,同一個(gè)抽象也可以有多個(gè)不同的實(shí)現(xiàn)误堡。就像前面示例的那樣,比如:站內(nèi)短消息的實(shí)現(xiàn)功能雏吭,可以被普通消息锁施、加急消息或是特急消息等不同的消息對象使用;反過來杖们,某個(gè)消息具體的發(fā)送方式悉抵,可以是站內(nèi)短消息,或者是Email摘完,也可以是手機(jī)短消息等具體的發(fā)送方式姥饰。

3.1.6:退化的橋接模式

如果Implementor僅有一個(gè)實(shí)現(xiàn),那么就沒有必要?jiǎng)?chuàng)建Implementor接口了孝治,這是一種橋接模式退化的情況列粪。這個(gè)時(shí)候Abstraction和Implementor是一對一的關(guān)系,雖然如此谈飒,也還是要保持它們的分離狀態(tài)篱竭,這樣的話,它們才不會(huì)相互影響步绸,才可以分別擴(kuò)展掺逼。

也就是說,就算不要Implementor接口了瓤介,也要保持Abstraction和Implementor是分離的吕喘,模式的分離機(jī)制仍然是非常有用的。

3.1.7:橋接模式和繼承

繼承是擴(kuò)展對象功能的一種常見手段刑桑,通常情況下氯质,繼承擴(kuò)展的功能變化緯度都是一緯的,也就是變化的因素只有一類祠斧。

對于出現(xiàn)變化因素有兩類的闻察,也就是有兩個(gè)變化緯度的情況,繼承實(shí)現(xiàn)就會(huì)比較痛苦。比如上面的示例辕漂,就有兩個(gè)變化緯度呢灶,一個(gè)是消息的類別,不同的消息類別處理不同钉嘹;另外一個(gè)是消息的發(fā)送方式鸯乃。

從理論上來說,如果用繼承的方式來實(shí)現(xiàn)這種有兩個(gè)變化緯度的情況跋涣,最后實(shí)際的實(shí)現(xiàn)類應(yīng)該是兩個(gè)緯度上可變數(shù)量的乘積那么多個(gè)缨睡。比如上面的示例,在消息類別的緯度上陈辱,目前的可變數(shù)量是3個(gè)奖年,普通消息、加急消息和特急消息沛贪;在消息發(fā)送方式的緯度上陋守,目前的可變數(shù)量也是3個(gè),站內(nèi)短消息鹏浅、Email和手機(jī)短消息嗅义。這種情況下屏歹,如果要實(shí)現(xiàn)全的話隐砸,那么需要的實(shí)現(xiàn)類應(yīng)該是:3 X 3 = 9個(gè)。

如果要在任何一個(gè)緯度上進(jìn)行擴(kuò)展蝙眶,都需要實(shí)現(xiàn)另外一個(gè)緯度上的可變數(shù)量那么多個(gè)實(shí)現(xiàn)類季希,這也是為何會(huì)感到擴(kuò)展起來很困難。而且隨著程序規(guī)模的加大幽纷,會(huì)越來越難以擴(kuò)展和維護(hù)式塌。

而橋接模式就是用來解決這種有兩個(gè)變化緯度的情況下,如何靈活的擴(kuò)展功能的一個(gè)很好的方案友浸。其實(shí)峰尝,橋接模式主要是把繼承改成了使用對象組合,從而把兩個(gè)緯度分開收恢,讓每一個(gè)緯度單獨(dú)去變化武学,最后通過對象組合的方式,把兩個(gè)緯度組合起來伦意,每一種組合的方式就相當(dāng)于原來繼承中的一種實(shí)現(xiàn)火窒,這樣就有效的減少了實(shí)際實(shí)現(xiàn)的類的個(gè)數(shù)。

從理論上來說驮肉,如果用橋接模式的方式來實(shí)現(xiàn)這種有兩個(gè)變化緯度的情況熏矿,最后實(shí)際的實(shí)現(xiàn)類應(yīng)該是兩個(gè)緯度上可變數(shù)量的和那么多個(gè)。同樣是上面那個(gè)示例,使用橋接模式來實(shí)現(xiàn)票编,實(shí)現(xiàn)全的話褪储,最后需要的實(shí)現(xiàn)類的數(shù)目應(yīng)該是: 3 +3= 6個(gè)。

這也從側(cè)面體現(xiàn)了栏妖,使用對象組合的方式比繼承要來得更靈活乱豆。

3.1.8:橋接模式的調(diào)用順序示意圖

3.2 誰來橋接

所謂誰來橋接,就是誰來負(fù)責(zé)創(chuàng)建抽象部分和實(shí)現(xiàn)部分的關(guān)系吊趾,說得更直白點(diǎn)宛裕,就是誰來負(fù)責(zé)創(chuàng)建Implementor的對象,并把它設(shè)置到抽象部分的對象里面去论泛,這點(diǎn)對于使用橋接模式來說揩尸,是十分重要的一點(diǎn)。

大致有如下幾種實(shí)現(xiàn)方式:

  • 1)由客戶端負(fù)責(zé)創(chuàng)建Implementor的對象屁奏,并在創(chuàng)建抽象部分的對象的時(shí)候岩榆,把它設(shè)置到抽象部分的對象里面去,前面的示例采用的就是這個(gè)方式
  • 2)可以在抽象部分的對象構(gòu)建的時(shí)候坟瓢,由抽象部分的對象自己來創(chuàng)建相應(yīng)的Implementor的對象勇边,當(dāng)然可以給它傳遞一些參數(shù),它可以根據(jù)參數(shù)來選擇并創(chuàng)建具體的Implementor的對象
  • 3)可以在Abstraction中選擇并創(chuàng)建一個(gè)缺省的Implementor的對象折联,然后子類可以根據(jù)需要改變這個(gè)實(shí)現(xiàn)
  • 4)也可以使用抽象工廠或者簡單工廠來選擇并創(chuàng)建具體的Implementor的對象粒褒,抽象部分的類可以通過調(diào)用工廠的方法來獲取Implementor的對象
  • 5)如果使用IoC/DI容器的話,還可以通過IoC/DI容器來創(chuàng)建具體的Implementor的對象诚镰,并注入回到Abstraction中

下面分別給出后面幾種實(shí)現(xiàn)方式的示例 :

  • 1)由抽象部分的對象自己來創(chuàng)建相應(yīng)的Implementor的對象分成兩種情況奕坟,一種是需要外部傳入?yún)?shù),一種是不需要外部傳入?yún)?shù)清笨。
  • 2)在Abstraction中創(chuàng)建缺省的Implementor對象對于這種方式月杉,實(shí)現(xiàn)比較簡單,直接在Abstraction的構(gòu)造方法中抠艾,創(chuàng)建一個(gè)缺省的 Implementor對象苛萎,然后子類根據(jù)需要,看是直接使用還是覆蓋掉检号。
  • 3)使用抽象工廠或者是簡單工廠對于這種方式腌歉,根據(jù)具體需要來選擇,如果是想要?jiǎng)?chuàng)建一系列實(shí)現(xiàn)對象谨敛,那就使用
    抽象工廠究履,如果是創(chuàng)建單個(gè)的實(shí)現(xiàn)對象,那就使用簡單工廠脸狸。這種方法的優(yōu)點(diǎn)是Abstraction類不用和任何一個(gè)Implementor類直接耦合最仑。
  • 4)使用 IoC/DI的方式對于這種方式藐俺,Abstraction的實(shí)現(xiàn)就更簡單了,只需要實(shí)現(xiàn)注入Implementor對象的方法就可以了泥彤,其它的Abstraction就不管了欲芹。
    IoC/DI容器會(huì)負(fù)責(zé)創(chuàng)建Implementor對象,并設(shè)置回到Abstraction對象中吟吝,使用 IoC/DI的方式菱父,并不會(huì)改變Abstraction和Implementor的關(guān)系,Abstraction同樣需要持有相應(yīng)的Implementor對象剑逃,同樣會(huì)把功能委托給Implementor對象去實(shí)現(xiàn)浙宜。

3.3 典型例子-JDBC

在Java應(yīng)用中,對于橋接模式有一個(gè)非常典型的例子蛹磺,就是:應(yīng)用程序使
用JDBC驅(qū)動(dòng)程序進(jìn)行開發(fā)的方式粟瞬。所謂驅(qū)動(dòng)程序,指的是按照預(yù)先約定好的接口來操作計(jì)算機(jī)系統(tǒng)或者是外圍設(shè)備的程序萤捆。

我們寫的應(yīng)用程序裙品,是面向JDBC的API在開發(fā),這些接口就相當(dāng)于橋接模式中的抽象部分的接口俗或。那么怎樣得到這些API的呢市怎?是通過DriverManager來得到的。此時(shí)的系統(tǒng)結(jié)構(gòu)如圖


那么這些JDBC的API辛慰,誰去實(shí)現(xiàn)呢区匠?光有接口,沒有實(shí)現(xiàn)也不行啊昆雀。

該驅(qū)動(dòng)程序登場了辱志,JDBC的驅(qū)動(dòng)程序?qū)崿F(xiàn)了JDBC的API蝠筑,驅(qū)動(dòng)程序就相當(dāng)于橋接模式中的具體實(shí)現(xiàn)部分狞膘。而且不同的數(shù)據(jù)庫,由于數(shù)據(jù)庫實(shí)現(xiàn)不一樣什乙,可執(zhí)行的Sql也不完全一樣挽封,因此對于JDBC驅(qū)動(dòng)的實(shí)現(xiàn)也是不一樣的,也就是不同的數(shù)據(jù)庫會(huì)有不同的驅(qū)動(dòng)實(shí)現(xiàn)臣镣。此時(shí)驅(qū)動(dòng)程序這邊的程序結(jié)構(gòu)如圖


有了抽象部分——JDBC的API辅愿,有了具體實(shí)現(xiàn)部分——驅(qū)動(dòng)程序,那么它們?nèi)绾芜B接起來呢忆某?就是如何橋接呢点待?

就是前面提到的DriverManager來把它們橋接起來,從某個(gè)側(cè)面來看弃舒, DriverManager在這里起到了類似于簡單工廠的功能癞埠,基于JDBC的應(yīng)用程序需要使用JDBC的API状原,如何得到呢?就通過DriverManager來獲取相應(yīng)的對象苗踪。

那么此時(shí)系統(tǒng)的整體結(jié)構(gòu)如圖


通過上圖可以看出颠区,基于JDBC的應(yīng)用程序,使用JDBC的API通铲,相當(dāng)于是對數(shù)據(jù)庫操作的抽象的擴(kuò)展毕莱,算作橋接模式的抽象部分;而具體的接口實(shí)現(xiàn)是由驅(qū)動(dòng)來完成的颅夺,驅(qū)動(dòng)這邊自然就相當(dāng)于橋接模式的實(shí)現(xiàn)部分了朋截。而橋接的方式,不再是讓抽象部分持有實(shí)現(xiàn)部分吧黄,而是采用了類似于工廠的做法质和,通過 DriverManager來把抽象部分和實(shí)現(xiàn)部分對接起來,從而實(shí)現(xiàn)抽象部分和實(shí)現(xiàn)部分解耦稚字。

JDBC的這種架構(gòu)饲宿,把抽象和具體分離開來,從而使得抽象和具體部分都可以獨(dú)立擴(kuò)展胆描。對于應(yīng)用程序而言瘫想,只要選用不同的驅(qū)動(dòng),就可以讓程序操作不同的數(shù)據(jù)庫昌讲,而無需更改應(yīng)用程序国夜,從而實(shí)現(xiàn)在不同的數(shù)據(jù)庫上移植;對于驅(qū)動(dòng)程序而言短绸,為數(shù)據(jù)庫實(shí)現(xiàn)不同的驅(qū)動(dòng)程序车吹,并不會(huì)影響應(yīng)用程序。而且醋闭,JDBC的這種架構(gòu)窄驹,還合理的劃分了應(yīng)用程序開發(fā)人員和驅(qū)動(dòng)程序開發(fā)人員的邊界。

3.4 廣義橋接-Java中無處不橋接

使用Java編寫程序证逻,一個(gè)很重要的原則就是“面向接口編程”乐埠,說得準(zhǔn)確
點(diǎn)應(yīng)該是“面向抽象編程”,由于在Java開發(fā)中囚企,更多的使用接口而非抽象類丈咐,因此通常就說成“面向接口編程”了。

接口把具體的實(shí)現(xiàn)和使用接口的客戶程序分離開來龙宏,從而使得具體的實(shí)現(xiàn)和使用接口的客戶程序可以分別擴(kuò)展棵逊,而不會(huì)相互影響。結(jié)構(gòu)如圖


可能有些朋友會(huì)覺得银酗,聽起來怎么像是橋接模式的功能呢辆影?沒錯(cuò)掩浙,如果把橋接模式的抽象部分先稍稍簡化一下,暫時(shí)不要RefinedAbstraction部分秸歧,那么就跟上面的結(jié)構(gòu)圖差不多了厨姚。去掉RefinedAbstraction后的簡化的橋接模式結(jié)構(gòu)示意圖如圖


是不是差不多呢?有朋友可能會(huì)覺得還是有很大差異键菱,差異主要在:前面接口的客戶程序是直接使用接口對象谬墙,不像橋接模式的抽象部分那樣,是持有具體實(shí)現(xiàn)部分的接口经备,這就導(dǎo)致畫出來的結(jié)構(gòu)圖拭抬,一個(gè)是依賴,一個(gè)是聚合關(guān)聯(lián)侵蒙。

請思考它們的本質(zhì)功能造虎,橋接模式中的抽象部分持有具體實(shí)現(xiàn)部分的接口,最終目的是什么纷闺,還不是需要通過調(diào)用具體實(shí)現(xiàn)部分的接口中的方法算凿,來完成一定的功能,這跟直接使用接口沒有什么不同犁功,只是表現(xiàn)形式有點(diǎn)不一樣涩赢。再說湃窍,前面那個(gè)使用接口的客戶程序也可以持有相應(yīng)的接口對象,這樣從形式上就一樣了材失。

也就是說和泌,從某個(gè)角度來講瘸恼,橋接模式不過就是對“面向抽象編程”這個(gè)設(shè)計(jì)原則的擴(kuò)展愁拭。正是通過具體實(shí)現(xiàn)的接口褪贵,把抽象部分和具體的實(shí)現(xiàn)分離開來,抽象部分相當(dāng)于是使用實(shí)現(xiàn)部分接口的客戶程序怒医,這樣抽象部分和實(shí)現(xiàn)部分就松散耦合了炉抒,從而可以實(shí)現(xiàn)相互獨(dú)立的變化。

這樣一來裆熙,幾乎可以把所有面向抽象編寫的程序端礼,都視作是橋接模式的體現(xiàn)禽笑,至少算是簡化的橋接模式入录,就算是廣義的橋接吧。而Java編程很強(qiáng)調(diào)“面向抽象編程”佳镜,因此僚稿,廣義的橋接,在 Java中可以說是無處不在蟀伸。

再舉個(gè)大家最熟悉的例子來示例一下蚀同。在Java應(yīng)用開發(fā)中缅刽,分層實(shí)現(xiàn)算是最基本的設(shè)計(jì)方式了吧,就拿大家最熟的三層架構(gòu)來說蠢络,表現(xiàn)層衰猛、邏輯層和數(shù)據(jù)層,或許有些朋友對它們稱呼的名稱不同刹孔,但都是同一回事情啡省。

三層的基本關(guān)系是表現(xiàn)層調(diào)用邏輯層,邏輯層調(diào)用數(shù)據(jù)層髓霞,通過什么來進(jìn)行調(diào)用呢卦睹?當(dāng)然是接口了,它們的基本結(jié)構(gòu)如圖


通過接口來進(jìn)行調(diào)用方库,使得表現(xiàn)層和邏輯層分離開來结序,也就是說表現(xiàn)層的變
化,不會(huì)影響到邏輯層纵潦,同理邏輯層的變化不會(huì)影響到表現(xiàn)層徐鹤。這也是同一套邏輯層和數(shù)據(jù)層,就能夠同時(shí)支持不同的表現(xiàn)層實(shí)現(xiàn)的原因邀层,比如支持Swing或Web方式的表現(xiàn)層凳干。

在邏輯層和數(shù)據(jù)層之間也是通過接口來調(diào)用,同樣使得邏輯層和數(shù)據(jù)層分離開被济,使得它們可以獨(dú)立的擴(kuò)展救赐。尤其是數(shù)據(jù)層,可能會(huì)有很多的實(shí)現(xiàn)方式只磷,比如:數(shù)據(jù)庫實(shí)現(xiàn)经磅、文件實(shí)現(xiàn)等,就算是數(shù)據(jù)庫實(shí)現(xiàn)钮追,又有針對不同數(shù)據(jù)庫的實(shí)現(xiàn)预厌,如Oracle、DB2等等元媚。

總之轧叽,通過面向抽象編程,三層架構(gòu)的各層都能夠獨(dú)立的擴(kuò)展和變化刊棕,而不會(huì)對其它層次產(chǎn)生影響炭晒。這正好是橋接模式的功能,實(shí)現(xiàn)抽象和實(shí)現(xiàn)的分離甥角,從而使得它們可以獨(dú)立的變化网严。當(dāng)然三層架構(gòu)不只是在一個(gè)地方使用橋接模式,而是至少在兩個(gè)地方來使用了橋接模式嗤无,一個(gè)在表現(xiàn)層和邏輯層之間震束,一個(gè)在邏輯層和數(shù)據(jù)層之間怜庸。

下面先分別看看這兩個(gè)使用橋接模式的地方的程序結(jié)構(gòu),然后再綜合起來看看整體的程序結(jié)構(gòu)垢村。先看看邏輯層和數(shù)據(jù)層之間的程序結(jié)構(gòu)割疾,如圖


再看看表現(xiàn)層和邏輯層之間的結(jié)構(gòu)示意,如圖


然后再把它們結(jié)合起來嘉栓,看看結(jié)合后的程序結(jié)構(gòu)


從廣義橋接模式的角度來看杈曲,平日熟悉的三層架構(gòu)其實(shí)就是在組合使用橋接模式。從這個(gè)圖還可以看出胸懈,橋接模式是可以連續(xù)組合使用的担扑,一個(gè)橋接模式的實(shí)現(xiàn)部分,可以作為下一個(gè)橋接模式的抽象部分趣钱。如此類推涌献,可以從三層架構(gòu)擴(kuò)展到四層、五層首有、直到N層架構(gòu)燕垃,都可以使用橋接模式來組合。

如果從更本質(zhì)的角度來看井联,基本上只要是面向抽象編寫的Java程序卜壕,都可以視為是橋接模式的應(yīng)用,都是讓抽象和實(shí)現(xiàn)相分離烙常,從而使它們能獨(dú)立的變化轴捎。不過只要分離的目的達(dá)到了,叫不叫橋接模式就無所謂了蚕脏。

3.5 橋接模式的優(yōu)缺點(diǎn)

  • 分離抽象和實(shí)現(xiàn)部分
  • 更好的擴(kuò)展性
  • 可動(dòng)態(tài)切換實(shí)現(xiàn)
  • 可減少子類的個(gè)數(shù)

4.思考橋接模式

4.1 橋接模式的本質(zhì)

橋接模式的本質(zhì)是:分離抽象和實(shí)現(xiàn)

4.2 對設(shè)計(jì)原則的體現(xiàn)

橋接模式很好的實(shí)現(xiàn)了開閉原則:通常應(yīng)用橋接模式的地方侦副,抽象部分和實(shí)現(xiàn)部分都是可變化的,也就是應(yīng)用會(huì)有兩個(gè)變化緯度驼鞭,橋接模式就是找到這兩個(gè)變化秦驯,并分別封裝起來,從而合理的實(shí)現(xiàn)OCP挣棕。

橋接模式還很好的體現(xiàn)了:多用對象組合译隘,少用對象繼承。

在前面的示例中洛心,如果使用對象繼承來擴(kuò)展功能固耘,不但讓對象之間有很強(qiáng)的耦合性,而且會(huì)需要很多的子類才能完成相應(yīng)的功能皂甘,前面已經(jīng)講述過了玻驻,需要兩個(gè)緯度上的可變化數(shù)量的乘積個(gè)子類。

采用對象的組合偿枕,松散了對象之間的耦合性璧瞬,不但使每個(gè)對象變得簡單和可維護(hù),還大大減少了子類的個(gè)數(shù)渐夸,根據(jù)前面的講述嗤锉,大約需要兩個(gè)緯度上的可變化數(shù)量的和這么多個(gè)子類。

4.3 何時(shí)選用

  • 1)如果你不希望在抽象和實(shí)現(xiàn)部分采用固定的綁定關(guān)系墓塌,可以采用橋接模式瘟忱,來把抽象和實(shí)現(xiàn)部分分開,然后在程序運(yùn)行期間來動(dòng)態(tài)的設(shè)置抽象部分需要用到的具體的實(shí)現(xiàn)苫幢,還可以動(dòng)態(tài)切換具體的實(shí)現(xiàn)
  • 2)如果出現(xiàn)抽象部分和實(shí)現(xiàn)部分都應(yīng)該可以擴(kuò)展的情況访诱,可以采用橋接模式,讓抽象部分和實(shí)現(xiàn)部分可以獨(dú)立的變化韩肝,從而可以靈活的進(jìn)行單獨(dú)擴(kuò)展触菜,而不是攪在一起,擴(kuò)展一邊會(huì)影響到另一邊哀峻。
  • 3)如果希望實(shí)現(xiàn)部分的修改涡相,不會(huì)對客戶產(chǎn)生影響,可以采用橋接模式剩蟀,客戶是面向抽象的接口在運(yùn)行催蝗,實(shí)現(xiàn)部分的修改,可以獨(dú)立于抽象部分育特,也就不會(huì)對客戶產(chǎn)生影響了丙号,也可以說對客戶是透明的
  • 4)如果采用繼承的實(shí)現(xiàn)方案,會(huì)導(dǎo)致產(chǎn)生很多子類缰冤,對于這種情況槽袄,可以考慮采用橋接模式,分析功能變化的原因锋谐,看看是否能分離成不同的緯度遍尺,然后通過橋接模式來分離它們,從而減少子類的數(shù)目

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末涮拗,一起剝皮案震驚了整個(gè)濱河市乾戏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌三热,老刑警劉巖鼓择,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異就漾,居然都是意外死亡呐能,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來摆出,“玉大人朗徊,你說我怎么就攤上這事≠寺” “怎么了爷恳?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長象踊。 經(jīng)常有香客問我温亲,道長,這世上最難降的妖魔是什么杯矩? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任栈虚,我火速辦了婚禮,結(jié)果婚禮上史隆,老公的妹妹穿的比我還像新娘魂务。我一直安慰自己,他們只是感情好逆害,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布头镊。 她就那樣靜靜地躺著,像睡著了一般魄幕。 火紅的嫁衣襯著肌膚如雪相艇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天纯陨,我揣著相機(jī)與錄音坛芽,去河邊找鬼。 笑死翼抠,一個(gè)胖子當(dāng)著我的面吹牛咙轩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播阴颖,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼活喊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了量愧?” 一聲冷哼從身側(cè)響起钾菊,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎偎肃,沒想到半個(gè)月后煞烫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡累颂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年滞详,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡料饥,死狀恐怖蒲犬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情稀火,我是刑警寧澤暖哨,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布赌朋,位于F島的核電站凰狞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏沛慢。R本人自食惡果不足惜赡若,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望团甲。 院中可真熱鬧逾冬,春花似錦、人聲如沸躺苦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽匹厘。三九已至嘀趟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間愈诚,已是汗流浹背她按。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炕柔,地道東北人酌泰。 一個(gè)月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像匕累,于是被迫代替她去往敵國和親陵刹。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容