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ù)目