1.初識橋接模式
將抽象部分與它的實現(xiàn)部分分離,使它們都可以獨(dú)立地變化。

- Abstraction:抽象部分的接口。通常在這個對象里面,要維護(hù)一個實現(xiàn)部分的對象引用,在抽象對象里面的方法,需要調(diào)用實現(xiàn)部分的對象來完成。這個對象里面的方法,通常都是跟具體的業(yè)務(wù)相關(guān)的方法。
RefinedAbstraction:擴(kuò)展抽象部分的接口,通常在這些對象里面,定義跟實際業(yè)務(wù)相關(guān)的方法,這些方法的實現(xiàn)通常會使用Abstraction中定義的方法,也可能需要調(diào)用實現(xiàn)部分的對象來完成。
Implementor:定義實現(xiàn)部分的接口,這個接口不用和Abstraction里面的方法一致,通常是由Implementor接口提供基本的操作,而Abstraction里面定義的是基于這些基本操作的業(yè)務(wù)方法,也就是說Abstraction定義了基于這些基本操作的較高層次的操作。
ConcreteImplementor:真正實現(xiàn)Implementor接口的對象。
2.體會橋接模式
2.1 場景問題——發(fā)送提示消息
考慮這樣一個實際的業(yè)務(wù)功能:發(fā)送提示消息?;旧纤袔I(yè)務(wù)流程處理的系統(tǒng)都會有這樣的功能,比如某人有新的工作了,需要發(fā)送一條消息提示他。
從業(yè)務(wù)上看,消息又分成普通消息、加急消息和特急消息多種,不同的消息類型,業(yè)務(wù)功能處理是不一樣的,比如加急消息是在消息上添加加急,而特急消息除了添加特急外,還會做一條催促的記錄,多久不完成會繼續(xù)催促。從發(fā)送消息的手段上看,又有系統(tǒng)內(nèi)短消息、手機(jī)短消息、郵件等等。
現(xiàn)在要實現(xiàn)這樣的發(fā)送提示消息的功能,該如何實現(xiàn)呢?
2.2 不用模式的解決方案
2.2.1 實現(xiàn)簡化版本
先考慮實現(xiàn)一個簡單點(diǎn)的版本,比如:消息先只是實現(xiàn)發(fā)送普通消息,發(fā)送的方式呢,先實現(xiàn)系統(tǒng)內(nèi)短消息和郵件。其它的功能,等這個版本完成過后,再繼續(xù)添加,這樣先把問題簡單化,實現(xiàn)起來會容易一點(diǎn)。
(1)由于發(fā)送普通消息會有兩種不同的實現(xiàn)方式,為了讓外部能統(tǒng)一操作,因此,把消息設(shè)計成接口,然后由兩個不同的實現(xiàn)類,分別實現(xiàn)系統(tǒng)內(nèi)短消息方式和郵件發(fā)送消息的方式。此時系統(tǒng)結(jié)構(gòu)如圖

2.2.2 實現(xiàn)發(fā)送加急消息
添加發(fā)送加急消息的功能,同樣是站內(nèi)短消息和 Email的方式。
加急消息的實現(xiàn)跟普通消息不同,加急消息會自動在消息上添加加急,然后再發(fā)送消息;另外加急消息會提供監(jiān)控的方法,讓客戶端可以隨時通過這個方法來了解對于加急消息處理的進(jìn)度等。因此加急消息需要擴(kuò)展出一個新的接口,除了基本的發(fā)送消息的功能,還需要添加監(jiān)控的功能,這個時候,系統(tǒng)的結(jié)構(gòu)如圖:

2.2.3 有何問題
上面這樣實現(xiàn),好像也能滿足基本的功能要求,可是這么實現(xiàn)好不好呢?有沒有什么問題呢?
咱們繼續(xù)向下來添加功能實現(xiàn),為了簡潔,就不再去進(jìn)行代碼示意了,通過實現(xiàn)的結(jié)構(gòu)示意圖就可以看出實現(xiàn)上的問題。
1.繼續(xù)添加特急消息的處理
特急消息不需要查看處理進(jìn)程,只要沒有完成,就直接催促,也就是說,對于特急消息,在普通消息的處理基礎(chǔ)上,需要添加催促的功能。而特急消息、還有催促的發(fā)送方式,相應(yīng)的實現(xiàn)方式還是發(fā)送站內(nèi)短消息和Email兩種,此時系統(tǒng)的結(jié)構(gòu)如圖:

仔細(xì)觀察上面的系統(tǒng)結(jié)構(gòu)示意圖,會發(fā)現(xiàn)一個很明顯的問題,那就是:通
過這種繼承的方式來擴(kuò)展消息處理,會非常不方便。
你看,實現(xiàn)加急消息處理的時候,必須實現(xiàn)站內(nèi)短消息和Email兩種處理方式,因為業(yè)務(wù)處理可能不同;在實現(xiàn)特急消息處理的時候,又必須實現(xiàn)站內(nèi)短消息和Email這兩種處理方式。
這意味著,以后每次擴(kuò)展一下消息處理,都必須要實現(xiàn)這兩種處理方式,是不是很痛苦,這還不算完,如果要添加新的實現(xiàn)方式呢?繼續(xù)向下看吧。
2.繼續(xù)添加發(fā)送手機(jī)消息的處理方式
如果看到上面的實現(xiàn),你還感覺問題不是很大的話,繼續(xù)完成功能,添加發(fā)送手機(jī)消息的處理方式。
仔細(xì)觀察現(xiàn)在的實現(xiàn),如果要添加一種新的發(fā)送消息的方式,是需要在每一種抽象的具體實現(xiàn)里面,都要添加發(fā)送手機(jī)消息的處理的。也就是說:發(fā)送普通消息、加急消息和特急消息的處理,都可以通過手機(jī)來發(fā)送。這就意味著,需要添加三個實現(xiàn)。此時系統(tǒng)結(jié)構(gòu)如圖:

3.小結(jié)一下出現(xiàn)的問題
采用通過繼承來擴(kuò)展的實現(xiàn)方式,有個明顯的缺點(diǎn):擴(kuò)展消息的種類不太容易,不同種類的消息具有不同的業(yè)務(wù),也就是有不同的實現(xiàn),在這種情況下,每個種類的消息,需要實現(xiàn)所有不同的消息發(fā)送方式。
更可怕的是,如果要新加入一種消息的發(fā)送方式,那么會要求所有的消息種類,都要加入這種新的發(fā)送方式的實現(xiàn)。
要是考慮業(yè)務(wù)功能上再擴(kuò)展一下呢?比如:要求實現(xiàn)群發(fā)消息,也就是一次可以發(fā)送多條消息,這就意味著很多地方都得修改,太恐怖了。
那么究竟該如何實現(xiàn)才能既實現(xiàn)功能,又能靈活的擴(kuò)展呢?
2.3 使用模式的解決方案
2.3.1 使用模式來解決的思路
仔細(xì)分析上面的示例,根據(jù)示例的功能要求,示例的變化具有兩個緯度,一個緯度是抽象的消息這邊,包括普通消息、加急消息和特急消息,這幾個抽象的消息本身就具有一定的關(guān)系,加急消息和特急消息會擴(kuò)展普通消息;另一個緯度在具體的消息發(fā)送方式上,包括站內(nèi)短消息、Email和手機(jī)短信息,這幾個方式是平等的,可被切換的方式。這兩個緯度一共可以組合出9種不同的可能性來,它們的關(guān)系如下圖

現(xiàn)在出現(xiàn)問題的根本原因,就在于消息的抽象和實現(xiàn)是混雜在一起的,這就導(dǎo)致了,一個緯度的變化,會引起另一個緯度進(jìn)行相應(yīng)的變化,從而使得程序擴(kuò)展起來非常困難。
要想解決這個問題,就必須把這兩個緯度分開,也就是將抽象部分和實現(xiàn)部分分開,讓它們相互獨(dú)立,這樣就可以實現(xiàn)獨(dú)立的變化,使擴(kuò)展變得簡單。
橋接模式通過引入實現(xiàn)的接口,把實現(xiàn)部分從系統(tǒng)中分離出去;那么,抽象這邊如何使用具體的實現(xiàn)呢?肯定是面向?qū)崿F(xiàn)的接口來編程了,為了讓抽象這邊能夠很方便的與實現(xiàn)結(jié)合起來,把頂層的抽象接口改成抽象類,在里面持有一個具體的實現(xiàn)部分的實例。
這樣一來,對于需要發(fā)送消息的客戶端而言,就只需要創(chuàng)建相應(yīng)的消息對象,然后調(diào)用這個消息對象的方法就可以了,這個消息對象會調(diào)用持有的真正的消息發(fā)送方式來把消息發(fā)送出去。也就是說客戶端只是想要發(fā)送消息而已,并不想關(guān)心具體如何發(fā)送。
2.3.2 使用模式來解決
首要任務(wù)是把抽象部分和實現(xiàn)部分分離出來,分析要實現(xiàn)的功能,抽象部分就是各個消息的類型所對應(yīng)的功能,而實現(xiàn)部分就是各種發(fā)送消息的方式。
其次要給抽象部分和實現(xiàn)部分分別定義接口,然后分別實現(xiàn)它們。
1.從簡單功能開始
從相對簡單的功能開始,先實現(xiàn)普通消息和加急消息的功能,發(fā)送方式先實現(xiàn)站內(nèi)短消息和Email這兩種。使用橋接模式來實現(xiàn)這些功能的程序結(jié)構(gòu)如圖

2.添加功能
看了上面的實現(xiàn),發(fā)現(xiàn)使用橋接模式來實現(xiàn)也不是很困難啊,關(guān)鍵得看是否能解決前面提出的問題,那就來添加還未實現(xiàn)的功能看看,添加對特急消息的處理,同時添加一個使用手機(jī)發(fā)送消息的方式。該怎么實現(xiàn)呢?
很簡單,只需要在抽象部分再添加一個特急消息的類,擴(kuò)展抽象消息就可以把特急消息的處理功能加入到系統(tǒng)中了;對于添加手機(jī)發(fā)送消息的方式也很簡單,在實現(xiàn)部分新增加一個實現(xiàn)類,實現(xiàn)用手機(jī)發(fā)送消息的方式,也就可以了。
這么簡單?好像看起來完全沒有了前面所提到的問題。的確如此,采用橋接模式來實現(xiàn)過后,抽象部分和實現(xiàn)部分分離開了,可以相互獨(dú)立的變化,而不會相互影響。因此在抽象部分添加新的消息處理,對發(fā)送消息的實現(xiàn)部分是沒有影響的;反過來增加發(fā)送消息的方式,對消息處理部分也是沒有影響的。
3.理解橋接模式
3.1 認(rèn)識橋接模式
3.1.1:什么是橋接
在橋接模式里面,不太好理解的就是橋接的概念,什么是橋接?為何需要橋接?如何橋接?把這些問題搞清楚了,也就基本明白橋接的含義了。
一個一個來,先看什么是橋接?所謂橋接,通俗點(diǎn)說就是在不同的東西之間搭一個橋,讓他們能夠連接起來,可以相互通訊和使用。那么在橋接模式中到底是給什么東西來搭橋呢?就是為被分離了的抽象部分和實現(xiàn)部分來搭橋,比如前面示例中抽象的消息和具體消息發(fā)送之間搭個橋。
但是這里要注意一個問題:在橋接模式中的橋接是單向的,也就是只能是抽象部分的對象去使用具體實現(xiàn)部分的對象,而不能反過來,也就是個單向橋。
3.1.2:為何需要橋接
為了達(dá)到讓抽象部分和實現(xiàn)部分都可以獨(dú)立變化的目的,在橋接模式中,是把抽象部分和實現(xiàn)部分分離開來的,雖然從程序結(jié)構(gòu)上是分開了,但是在抽象部分實現(xiàn)的時候,還是需要使用具體的實現(xiàn)的,這可怎么辦呢?抽象部分如何才能調(diào)用到具體實現(xiàn)部分的功能呢?很簡單,搭個橋不就可以了,搭個橋,讓抽象部分通過這個橋就可以調(diào)用到實現(xiàn)部分的功能了,因此需要橋接。
3.1.3:如何橋接
這個理解上也很簡單,只要讓抽象部分擁有實現(xiàn)部分的接口對象,這就橋接上了,在抽象部分就可以通過這個接口來調(diào)用具體實現(xiàn)部分的功能。也就是說,橋接在程序上就體現(xiàn)成了在抽象部分擁有實現(xiàn)部分的接口對象,維護(hù)橋接就是維護(hù)這個關(guān)系。
3.1.4:獨(dú)立變化
橋接模式的意圖:使得抽象和實現(xiàn)可以獨(dú)立變化,都可以分別擴(kuò)充。也就是說抽象部分和實現(xiàn)部分是一種非常松散的關(guān)系,從某個角度來講,抽象部分和實現(xiàn)部分是可以完全分開的,獨(dú)立的,抽象部分不過是一個使用實現(xiàn)部分對外接口的程序罷了。
如果這么看橋接模式的話,就類似于策略模式了,抽象部分需要根據(jù)某個策略,來選擇真實的實現(xiàn),也就是說橋接模式的抽象部分相當(dāng)于策略模式的上下文。更原始的就直接類似于面向接口編程,通過接口分離的兩個部分而已。但是別忘了,橋接模式的抽象部分,是可以繼續(xù)擴(kuò)展和變化的,而策略模式只有上下文,是不存在所謂抽象部分的。
那抽象和實現(xiàn)為何還要組合在一起呢?原因是在抽象部分和實現(xiàn)部分還是存在內(nèi)部聯(lián)系的,抽象部分的實現(xiàn)通常是需要調(diào)用實現(xiàn)部分的功能來實現(xiàn)的
3.1.5:動態(tài)變換功能
由于橋接模式中的抽象部分和實現(xiàn)部分是完全分離的,因此可以在運(yùn)行時動態(tài)組合具體的真實實現(xiàn),從而達(dá)到動態(tài)變換功能的目的。
從另外一個角度看,抽象部分和實現(xiàn)部分沒有固定的綁定關(guān)系了,因此同一個真實實現(xiàn)可以被不同的抽象對象使用,反過來,同一個抽象也可以有多個不同的實現(xiàn)。就像前面示例的那樣,比如:站內(nèi)短消息的實現(xiàn)功能,可以被普通消息、加急消息或是特急消息等不同的消息對象使用;反過來,某個消息具體的發(fā)送方式,可以是站內(nèi)短消息,或者是Email,也可以是手機(jī)短消息等具體的發(fā)送方式。
3.1.6:退化的橋接模式
如果Implementor僅有一個實現(xiàn),那么就沒有必要創(chuàng)建Implementor接口了,這是一種橋接模式退化的情況。這個時候Abstraction和Implementor是一對一的關(guān)系,雖然如此,也還是要保持它們的分離狀態(tài),這樣的話,它們才不會相互影響,才可以分別擴(kuò)展。
也就是說,就算不要Implementor接口了,也要保持Abstraction和Implementor是分離的,模式的分離機(jī)制仍然是非常有用的。
3.1.7:橋接模式和繼承
繼承是擴(kuò)展對象功能的一種常見手段,通常情況下,繼承擴(kuò)展的功能變化緯度都是一緯的,也就是變化的因素只有一類。
對于出現(xiàn)變化因素有兩類的,也就是有兩個變化緯度的情況,繼承實現(xiàn)就會比較痛苦。比如上面的示例,就有兩個變化緯度,一個是消息的類別,不同的消息類別處理不同;另外一個是消息的發(fā)送方式。
從理論上來說,如果用繼承的方式來實現(xiàn)這種有兩個變化緯度的情況,最后實際的實現(xiàn)類應(yīng)該是兩個緯度上可變數(shù)量的乘積那么多個。比如上面的示例,在消息類別的緯度上,目前的可變數(shù)量是3個,普通消息、加急消息和特急消息;在消息發(fā)送方式的緯度上,目前的可變數(shù)量也是3個,站內(nèi)短消息、Email和手機(jī)短消息。這種情況下,如果要實現(xiàn)全的話,那么需要的實現(xiàn)類應(yīng)該是:3 X 3 = 9個。
如果要在任何一個緯度上進(jìn)行擴(kuò)展,都需要實現(xiàn)另外一個緯度上的可變數(shù)量那么多個實現(xiàn)類,這也是為何會感到擴(kuò)展起來很困難。而且隨著程序規(guī)模的加大,會越來越難以擴(kuò)展和維護(hù)。
而橋接模式就是用來解決這種有兩個變化緯度的情況下,如何靈活的擴(kuò)展功能的一個很好的方案。其實,橋接模式主要是把繼承改成了使用對象組合,從而把兩個緯度分開,讓每一個緯度單獨(dú)去變化,最后通過對象組合的方式,把兩個緯度組合起來,每一種組合的方式就相當(dāng)于原來繼承中的一種實現(xiàn),這樣就有效的減少了實際實現(xiàn)的類的個數(shù)。
從理論上來說,如果用橋接模式的方式來實現(xiàn)這種有兩個變化緯度的情況,最后實際的實現(xiàn)類應(yīng)該是兩個緯度上可變數(shù)量的和那么多個。同樣是上面那個示例,使用橋接模式來實現(xiàn),實現(xiàn)全的話,最后需要的實現(xiàn)類的數(shù)目應(yīng)該是: 3 +3= 6個。
這也從側(cè)面體現(xiàn)了,使用對象組合的方式比繼承要來得更靈活。
3.1.8:橋接模式的調(diào)用順序示意圖

3.2 誰來橋接
所謂誰來橋接,就是誰來負(fù)責(zé)創(chuàng)建抽象部分和實現(xiàn)部分的關(guān)系,說得更直白點(diǎn),就是誰來負(fù)責(zé)創(chuàng)建Implementor的對象,并把它設(shè)置到抽象部分的對象里面去,這點(diǎn)對于使用橋接模式來說,是十分重要的一點(diǎn)。
大致有如下幾種實現(xiàn)方式:
- 1)由客戶端負(fù)責(zé)創(chuàng)建Implementor的對象,并在創(chuàng)建抽象部分的對象的時候,把它設(shè)置到抽象部分的對象里面去,前面的示例采用的就是這個方式
- 2)可以在抽象部分的對象構(gòu)建的時候,由抽象部分的對象自己來創(chuàng)建相應(yīng)的Implementor的對象,當(dāng)然可以給它傳遞一些參數(shù),它可以根據(jù)參數(shù)來選擇并創(chuàng)建具體的Implementor的對象
- 3)可以在Abstraction中選擇并創(chuàng)建一個缺省的Implementor的對象,然后子類可以根據(jù)需要改變這個實現(xiàn)
- 4)也可以使用抽象工廠或者簡單工廠來選擇并創(chuàng)建具體的Implementor的對象,抽象部分的類可以通過調(diào)用工廠的方法來獲取Implementor的對象
- 5)如果使用IoC/DI容器的話,還可以通過IoC/DI容器來創(chuàng)建具體的Implementor的對象,并注入回到Abstraction中
下面分別給出后面幾種實現(xiàn)方式的示例 :
- 1)由抽象部分的對象自己來創(chuàng)建相應(yīng)的Implementor的對象分成兩種情況,一種是需要外部傳入?yún)?shù),一種是不需要外部傳入?yún)?shù)。
- 2)在Abstraction中創(chuàng)建缺省的Implementor對象對于這種方式,實現(xiàn)比較簡單,直接在Abstraction的構(gòu)造方法中,創(chuàng)建一個缺省的 Implementor對象,然后子類根據(jù)需要,看是直接使用還是覆蓋掉。
- 3)使用抽象工廠或者是簡單工廠對于這種方式,根據(jù)具體需要來選擇,如果是想要創(chuàng)建一系列實現(xiàn)對象,那就使用
抽象工廠,如果是創(chuàng)建單個的實現(xiàn)對象,那就使用簡單工廠。這種方法的優(yōu)點(diǎn)是Abstraction類不用和任何一個Implementor類直接耦合。 - 4)使用 IoC/DI的方式對于這種方式,Abstraction的實現(xiàn)就更簡單了,只需要實現(xiàn)注入Implementor對象的方法就可以了,其它的Abstraction就不管了。
IoC/DI容器會負(fù)責(zé)創(chuàng)建Implementor對象,并設(shè)置回到Abstraction對象中,使用 IoC/DI的方式,并不會改變Abstraction和Implementor的關(guān)系,Abstraction同樣需要持有相應(yīng)的Implementor對象,同樣會把功能委托給Implementor對象去實現(xiàn)。
3.3 典型例子-JDBC
在Java應(yīng)用中,對于橋接模式有一個非常典型的例子,就是:應(yīng)用程序使
用JDBC驅(qū)動程序進(jìn)行開發(fā)的方式。所謂驅(qū)動程序,指的是按照預(yù)先約定好的接口來操作計算機(jī)系統(tǒng)或者是外圍設(shè)備的程序。
我們寫的應(yīng)用程序,是面向JDBC的API在開發(fā),這些接口就相當(dāng)于橋接模式中的抽象部分的接口。那么怎樣得到這些API的呢?是通過DriverManager來得到的。此時的系統(tǒng)結(jié)構(gòu)如圖

那么這些JDBC的API,誰去實現(xiàn)呢?光有接口,沒有實現(xiàn)也不行啊。
該驅(qū)動程序登場了,JDBC的驅(qū)動程序?qū)崿F(xiàn)了JDBC的API,驅(qū)動程序就相當(dāng)于橋接模式中的具體實現(xiàn)部分。而且不同的數(shù)據(jù)庫,由于數(shù)據(jù)庫實現(xiàn)不一樣,可執(zhí)行的Sql也不完全一樣,因此對于JDBC驅(qū)動的實現(xiàn)也是不一樣的,也就是不同的數(shù)據(jù)庫會有不同的驅(qū)動實現(xiàn)。此時驅(qū)動程序這邊的程序結(jié)構(gòu)如圖

有了抽象部分——JDBC的API,有了具體實現(xiàn)部分——驅(qū)動程序,那么它們?nèi)绾芜B接起來呢?就是如何橋接呢?
就是前面提到的DriverManager來把它們橋接起來,從某個側(cè)面來看, DriverManager在這里起到了類似于簡單工廠的功能,基于JDBC的應(yīng)用程序需要使用JDBC的API,如何得到呢?就通過DriverManager來獲取相應(yīng)的對象。
那么此時系統(tǒng)的整體結(jié)構(gòu)如圖

通過上圖可以看出,基于JDBC的應(yīng)用程序,使用JDBC的API,相當(dāng)于是對數(shù)據(jù)庫操作的抽象的擴(kuò)展,算作橋接模式的抽象部分;而具體的接口實現(xiàn)是由驅(qū)動來完成的,驅(qū)動這邊自然就相當(dāng)于橋接模式的實現(xiàn)部分了。而橋接的方式,不再是讓抽象部分持有實現(xiàn)部分,而是采用了類似于工廠的做法,通過 DriverManager來把抽象部分和實現(xiàn)部分對接起來,從而實現(xiàn)抽象部分和實現(xiàn)部分解耦。
JDBC的這種架構(gòu),把抽象和具體分離開來,從而使得抽象和具體部分都可以獨(dú)立擴(kuò)展。對于應(yīng)用程序而言,只要選用不同的驅(qū)動,就可以讓程序操作不同的數(shù)據(jù)庫,而無需更改應(yīng)用程序,從而實現(xiàn)在不同的數(shù)據(jù)庫上移植;對于驅(qū)動程序而言,為數(shù)據(jù)庫實現(xiàn)不同的驅(qū)動程序,并不會影響應(yīng)用程序。而且,JDBC的這種架構(gòu),還合理的劃分了應(yīng)用程序開發(fā)人員和驅(qū)動程序開發(fā)人員的邊界。
3.4 廣義橋接-Java中無處不橋接
使用Java編寫程序,一個很重要的原則就是“面向接口編程”,說得準(zhǔn)確
點(diǎn)應(yīng)該是“面向抽象編程”,由于在Java開發(fā)中,更多的使用接口而非抽象類,因此通常就說成“面向接口編程”了。
接口把具體的實現(xiàn)和使用接口的客戶程序分離開來,從而使得具體的實現(xiàn)和使用接口的客戶程序可以分別擴(kuò)展,而不會相互影響。結(jié)構(gòu)如圖

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

是不是差不多呢?有朋友可能會覺得還是有很大差異,差異主要在:前面接口的客戶程序是直接使用接口對象,不像橋接模式的抽象部分那樣,是持有具體實現(xiàn)部分的接口,這就導(dǎo)致畫出來的結(jié)構(gòu)圖,一個是依賴,一個是聚合關(guān)聯(lián)。
請思考它們的本質(zhì)功能,橋接模式中的抽象部分持有具體實現(xiàn)部分的接口,最終目的是什么,還不是需要通過調(diào)用具體實現(xiàn)部分的接口中的方法,來完成一定的功能,這跟直接使用接口沒有什么不同,只是表現(xiàn)形式有點(diǎn)不一樣。再說,前面那個使用接口的客戶程序也可以持有相應(yīng)的接口對象,這樣從形式上就一樣了。
也就是說,從某個角度來講,橋接模式不過就是對“面向抽象編程”這個設(shè)計原則的擴(kuò)展。正是通過具體實現(xiàn)的接口,把抽象部分和具體的實現(xiàn)分離開來,抽象部分相當(dāng)于是使用實現(xiàn)部分接口的客戶程序,這樣抽象部分和實現(xiàn)部分就松散耦合了,從而可以實現(xiàn)相互獨(dú)立的變化。
這樣一來,幾乎可以把所有面向抽象編寫的程序,都視作是橋接模式的體現(xiàn),至少算是簡化的橋接模式,就算是廣義的橋接吧。而Java編程很強(qiáng)調(diào)“面向抽象編程”,因此,廣義的橋接,在 Java中可以說是無處不在。
再舉個大家最熟悉的例子來示例一下。在Java應(yīng)用開發(fā)中,分層實現(xiàn)算是最基本的設(shè)計方式了吧,就拿大家最熟的三層架構(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)層的變
化,不會影響到邏輯層,同理邏輯層的變化不會影響到表現(xiàn)層。這也是同一套邏輯層和數(shù)據(jù)層,就能夠同時支持不同的表現(xiàn)層實現(xiàn)的原因,比如支持Swing或Web方式的表現(xiàn)層。
在邏輯層和數(shù)據(jù)層之間也是通過接口來調(diào)用,同樣使得邏輯層和數(shù)據(jù)層分離開,使得它們可以獨(dú)立的擴(kuò)展。尤其是數(shù)據(jù)層,可能會有很多的實現(xiàn)方式,比如:數(shù)據(jù)庫實現(xiàn)、文件實現(xiàn)等,就算是數(shù)據(jù)庫實現(xiàn),又有針對不同數(shù)據(jù)庫的實現(xiàn),如Oracle、DB2等等。
總之,通過面向抽象編程,三層架構(gòu)的各層都能夠獨(dú)立的擴(kuò)展和變化,而不會對其它層次產(chǎn)生影響。這正好是橋接模式的功能,實現(xiàn)抽象和實現(xiàn)的分離,從而使得它們可以獨(dú)立的變化。當(dāng)然三層架構(gòu)不只是在一個地方使用橋接模式,而是至少在兩個地方來使用了橋接模式,一個在表現(xiàn)層和邏輯層之間,一個在邏輯層和數(shù)據(jù)層之間。
下面先分別看看這兩個使用橋接模式的地方的程序結(jié)構(gòu),然后再綜合起來看看整體的程序結(jié)構(gòu)。先看看邏輯層和數(shù)據(jù)層之間的程序結(jié)構(gòu),如圖

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

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

從廣義橋接模式的角度來看,平日熟悉的三層架構(gòu)其實就是在組合使用橋接模式。從這個圖還可以看出,橋接模式是可以連續(xù)組合使用的,一個橋接模式的實現(xiàn)部分,可以作為下一個橋接模式的抽象部分。如此類推,可以從三層架構(gòu)擴(kuò)展到四層、五層、直到N層架構(gòu),都可以使用橋接模式來組合。
如果從更本質(zhì)的角度來看,基本上只要是面向抽象編寫的Java程序,都可以視為是橋接模式的應(yīng)用,都是讓抽象和實現(xiàn)相分離,從而使它們能獨(dú)立的變化。不過只要分離的目的達(dá)到了,叫不叫橋接模式就無所謂了。
3.5 橋接模式的優(yōu)缺點(diǎn)
- 分離抽象和實現(xiàn)部分
- 更好的擴(kuò)展性
- 可動態(tài)切換實現(xiàn)
- 可減少子類的個數(shù)
4.思考橋接模式
4.1 橋接模式的本質(zhì)
橋接模式的本質(zhì)是:分離抽象和實現(xiàn)
4.2 對設(shè)計原則的體現(xiàn)
橋接模式很好的實現(xiàn)了開閉原則:通常應(yīng)用橋接模式的地方,抽象部分和實現(xiàn)部分都是可變化的,也就是應(yīng)用會有兩個變化緯度,橋接模式就是找到這兩個變化,并分別封裝起來,從而合理的實現(xiàn)OCP。
橋接模式還很好的體現(xiàn)了:多用對象組合,少用對象繼承。
在前面的示例中,如果使用對象繼承來擴(kuò)展功能,不但讓對象之間有很強(qiáng)的耦合性,而且會需要很多的子類才能完成相應(yīng)的功能,前面已經(jīng)講述過了,需要兩個緯度上的可變化數(shù)量的乘積個子類。
采用對象的組合,松散了對象之間的耦合性,不但使每個對象變得簡單和可維護(hù),還大大減少了子類的個數(shù),根據(jù)前面的講述,大約需要兩個緯度上的可變化數(shù)量的和這么多個子類。
4.3 何時選用
- 1)如果你不希望在抽象和實現(xiàn)部分采用固定的綁定關(guān)系,可以采用橋接模式,來把抽象和實現(xiàn)部分分開,然后在程序運(yùn)行期間來動態(tài)的設(shè)置抽象部分需要用到的具體的實現(xiàn),還可以動態(tài)切換具體的實現(xiàn)
- 2)如果出現(xiàn)抽象部分和實現(xiàn)部分都應(yīng)該可以擴(kuò)展的情況,可以采用橋接模式,讓抽象部分和實現(xiàn)部分可以獨(dú)立的變化,從而可以靈活的進(jìn)行單獨(dú)擴(kuò)展,而不是攪在一起,擴(kuò)展一邊會影響到另一邊。
- 3)如果希望實現(xiàn)部分的修改,不會對客戶產(chǎn)生影響,可以采用橋接模式,客戶是面向抽象的接口在運(yùn)行,實現(xiàn)部分的修改,可以獨(dú)立于抽象部分,也就不會對客戶產(chǎn)生影響了,也可以說對客戶是透明的
- 4)如果采用繼承的實現(xiàn)方案,會導(dǎo)致產(chǎn)生很多子類,對于這種情況,可以考慮采用橋接模式,分析功能變化的原因,看看是否能分離成不同的緯度,然后通過橋接模式來分離它們,從而減少子類的數(shù)目