定義
橋梁模式是對象的結(jié)構(gòu)模式。又稱為柄體(Handle and Body)模式或接口(Interface)模式。橋梁模式的用意是“將抽象化(Abstraction)與實現(xiàn)化(Implementation)脫耦,使得二者獨立的變化”。
橋梁模式的用意
橋梁模式雖然不是一個使用頻率很高的模式特碳,但是熟悉這個模式對于理解面向?qū)ο蟮脑O(shè)計原則,包括“開-閉”原則以及組合/聚合復(fù)用原則都很有幫助晕换,理解好這兩個原則午乓,有助于形成正確的設(shè)計思想和培養(yǎng)良好的設(shè)計風(fēng)格。
橋梁模式的用意是“將抽象化(Abstraction)與實現(xiàn)化(Implementation)脫耦闸准,使得二者獨立的變化”益愈。這句話很短,但是第一次讀到這句話的人很可能都會思考良久而不得其解夷家。
這句話有三個關(guān)鍵詞蒸其,也就是抽象化、實現(xiàn)化和脫耦库快。理解這三個詞所代表的概念是理解橋梁模式用意的關(guān)鍵枣接。
抽象化
從眾多的事物中抽取出共同的、本質(zhì)性的特征缺谴,而舍棄其非本質(zhì)的特征但惶,就是抽象化。例如:蘋果湿蛔、香蕉膀曾、梨、桃子等阳啥。它們共同的特征就是水果添谊。得出水果概念的過程,就是一個抽象化的過程察迟。要抽象斩狱,就必須進(jìn)行比較,沒有比較就無法找到在本質(zhì)上共同的部分扎瓶。共同特征是指那些能把一類事物與他類事物分開來的特征所踊,這些具有區(qū)分作用的特征?又稱為本質(zhì)特征。因此抽取事物的共同特征就是抽取事物的本質(zhì)特征概荷,舍棄非本質(zhì)的特征秕岛。所以抽象化的過程也是一個裁剪的過程。在抽象時,同與不同继薛,決定于從什么角度上來抽象修壕。抽象的角度決定于分析問題的目的。
通常情況下遏考,一組對象如果具有相同的特征慈鸠,那么它們就可以通過一個共同的類來描述。如果一些類具有相同的特征灌具,往往可以通過一個共同的抽象類來描述青团。
實現(xiàn)化
抽象化給出的具體實現(xiàn),就是實現(xiàn)化稽亏。
一個類的實例就是這個類的實例化壶冒,一個具體子類就是它的抽象超類的實例化。
脫耦
所謂耦合截歉,就是兩個實體的行為的某種強(qiáng)關(guān)聯(lián)胖腾。而將它們的強(qiáng)關(guān)聯(lián)去掉,就是耦合的脫耦瘪松。在這里咸作,脫耦是指將抽象化和實例化之間的耦合解脫開,或者是將它們之間的強(qiáng)關(guān)聯(lián)改成弱關(guān)聯(lián)宵睦。
所謂強(qiáng)關(guān)聯(lián)记罚,就是在編譯時期已經(jīng)確定的,無法在運行時期動態(tài)改變的關(guān)聯(lián)壳嚎;所謂弱關(guān)聯(lián)桐智,就是可以動態(tài)確定并且可以在運行時期動態(tài)改變的關(guān)聯(lián)。顯然烟馅,在java語言中说庭,繼承關(guān)系是強(qiáng)關(guān)聯(lián),而聚合關(guān)系是弱關(guān)聯(lián)郑趁。
將兩個角色之間的繼承關(guān)系改為聚合關(guān)系刊驴,就是將它們之間的強(qiáng)關(guān)聯(lián)改換成為弱關(guān)聯(lián),因此寡润,橋梁模式中的所謂脫耦捆憎,就是指在一個軟件系統(tǒng)中的抽象化和實現(xiàn)化之間使用聚合關(guān)系,而不是繼承關(guān)系梭纹,從而使兩者可以相對對立的變化躲惰。
這就是橋梁模式的用意。
橋梁模式的結(jié)構(gòu)
下圖所示就是一個實現(xiàn)了橋梁模式的示意性系統(tǒng)的結(jié)構(gòu)圖:
可以看出栗柒,這個系統(tǒng)含有兩個等級結(jié)構(gòu)
- 由抽象化角色和修正抽象化角色組成的抽象化等級結(jié)構(gòu)礁扮。
- 由實現(xiàn)化角色和兩個具體實現(xiàn)化角色所組成的實現(xiàn)化等級結(jié)構(gòu)知举。
橋梁模式涉及的角色有:
- 抽象化角色(Abstraction):抽象化給出的定義瞬沦,并保存一個對實現(xiàn)化對象的引用太伊。
- 修正抽象化角色(RefineAbstraction):拓展抽象化角色,改變和修正父類對抽象化的定義逛钻。
- 實現(xiàn)化角色(Implementor):這個角色給出實現(xiàn)化角色的接口僚焦,但是不給出具體的實現(xiàn)。必須指出的是曙痘,這個接口不一定和抽象化角色的定義相同芳悲,實際上,這兩個接口可以非常不一樣边坤。實現(xiàn)化角色應(yīng)當(dāng)只給出底層操作名扛,而抽象化角色應(yīng)當(dāng)只給出基于底層操作的更高一層的操作。
- 具體實現(xiàn)化角色(ConcreteImplementor):這個角色給出實現(xiàn)化角色接口的具體實現(xiàn)茧痒。
抽象化角色就像是一個水杯的手柄肮韧,而實現(xiàn)化角色和具體實現(xiàn)化角色就像是水杯的被神。手柄控制杯身旺订,這就是此模式別名“柄體”的來源弄企。
對象是行為的封裝,而行為是由方法實現(xiàn)的区拳。在這個示意性系統(tǒng)里拘领,抽象化等級結(jié)構(gòu)中的類封裝了operation()
方法;而實現(xiàn)化等級結(jié)構(gòu)中的類封裝的是operationImpl()
方法樱调。當(dāng)然约素,在實際的系統(tǒng)中往往會有多于一個的方法。
抽象化等級結(jié)構(gòu)中的方法通過向?qū)?yīng)的實現(xiàn)化對象的委派實現(xiàn)自己的功能笆凌,這意味著抽象化角色可以通過向不同的實現(xiàn)化對象委派圣猎,來達(dá)到動態(tài)的轉(zhuǎn)換自己的功能的目的。
示例代碼
實現(xiàn)化角色
public abstract class Implementor {
/**
* 抽象方法菩颖,實現(xiàn)抽象部分需要的某些功能
*/
public abstract void operationImpl();
}
具體實現(xiàn)化角色
public class ConcreteImplementorA extends Implementor {
@Override
public void operationImpl() {
//具體操作
}
}
public class ConcreteImplementorB extends Implementor {
@Override
public void operationImpl() {
//具體操作
}
}
抽象化角色類样漆,它聲明了一個方法operation()
,并給出了它的實現(xiàn)晦闰。這個實現(xiàn)是通過向?qū)崿F(xiàn)化對象的委派(也就是調(diào)用實現(xiàn)化對象的operationImpl()
方法)實現(xiàn)的放祟。
public abstract class Abstraction {
protected Implementor implementor;
public Abstraction(Implementor implementor) {
this.implementor = implementor;
}
/**
* 示例方法
*/
public void operation() {
implementor.operationImpl();
}
}
修正抽象化角色
public class RefinedAbstraction extends Abstraction {
public RefinedAbstraction(Implementor implementor) {
super(implementor);
}
/**
* 其他的操作方法
*/
public void otherOperation() {
}
}
一般而言,實現(xiàn)化角色中的每個方法都應(yīng)當(dāng)有一個抽象化角色中的某一個方法與之對應(yīng)呻右,但是反過來則不一定跪妥。換而言之,抽象化角色的接口比實現(xiàn)化角色的接口寬声滥。抽象化角色除了提供與實現(xiàn)化角色相關(guān)的方法之外眉撵,還有可能提供其他的方法侦香;而實現(xiàn)化角色則往往僅為實現(xiàn)抽象化角色的相關(guān)行為而存在。
使用場景
考慮這樣一個實際的業(yè)務(wù)功能:發(fā)送提示消息纽疟」藓基本上所有帶業(yè)務(wù)流程處理的系統(tǒng)都會有這樣的功能,比如OA上有尚未處理完畢的文件污朽,需要發(fā)送一條消息提示他散吵。
從業(yè)務(wù)上看,消息又分為普通消息蟆肆、加急消息和特急消息多種矾睦,不同的消息類型,業(yè)務(wù)功能處理是不一樣的炎功,比如加急消息是在消息上添加加急枚冗,而特急消息除了添加特急外,還會做一條催促的記錄蛇损,多久不完成會繼續(xù)催促赁温;從發(fā)送消息的手段上看,又有系統(tǒng)內(nèi)短消息州藕、手機(jī)短消息束世、郵件等。
不使用模式的解決方案
實現(xiàn)發(fā)送普通消息
先考慮實現(xiàn)一個簡單點的版本床玻,比如毁涉,消息只是實現(xiàn)發(fā)送普通消息,發(fā)送的方式只實現(xiàn)系統(tǒng)內(nèi)短消息和郵件锈死。其他的功能贫堰,等這個版本完成后,再繼續(xù)添加待牵。
示例代碼
消息的統(tǒng)一接口
public interface Message {
/**
* 發(fā)送消息
* @param message 發(fā)送消息的內(nèi)容
* @param toUser 接收人
*/
void send (String message, String toUser);
}
系統(tǒng)內(nèi)短消息示例類
public class CommonMessageSMS implements Message {
@Override
public void send(String message, String toUser) {
System.out.println(String.format("使用系統(tǒng)內(nèi)部短消息的方法其屏,發(fā)送消息 %s 給 %s", message, toUser));
}
}
郵件短消息示例類
public class CommonMessgeEmail implements Message {
@Override
public void send(String message, String toUser) {
System.out.println(String.format("使用郵件短消息的方法,發(fā)送消息 %s 給 %s", message, toUser));
}
}
實現(xiàn)發(fā)送加急消息
發(fā)送加急消息同樣有兩種方式缨该,系統(tǒng)內(nèi)短消息和郵件方式偎行。但是加急消息的實現(xiàn)不同于普通消息,加急消息會自動在消息上添加加急贰拿,然后再發(fā)送消息蛤袒;另外加急消息會提供監(jiān)控的方法,讓客戶端可以隨時通過這個方法來了解對于加急消息的處理進(jìn)度膨更。比如妙真,相應(yīng)的人員是否接收到這個消息,相應(yīng)的處理工作是否已經(jīng)展開荚守。因此加急消息需要拓展出一個新的接口珍德,除了基本的發(fā)送消息的功能练般,還需要添加監(jiān)控功能。
示例代碼
加急消息的接口
public interface UrgencyMessage extends Message {
/**
* 監(jiān)控指定消息的處理過程
* @param messageId 被監(jiān)控的消息編號
* @return 監(jiān)控到的消息的處理狀態(tài)
*/
Object watch(String messageId);
}
系統(tǒng)內(nèi)加急短消息示例類
public class UrgencyMessageSMS implements UrgencyMessage {
@Override
public void send(String message, String toUser) {
message = "加急:" + message;
System.out.println(String.format("使用系統(tǒng)內(nèi)部短消息的方法锈候,發(fā)送消息 %s 給 %s", message, toUser));
}
@Override
public Object watch(String messageId) {
//根據(jù)消息編碼獲取消息的狀態(tài)薄料,組成監(jiān)控的數(shù)據(jù)對象,然后返回
return null;
}
}
郵件加急短消息示例類
public class UrgencyMessageEmail implements UrgencyMessage {
@Override
public void send(String message, String toUser) {
message = "加急:" + message;
System.out.println(String.format("使用郵件短消息的方法晴及,發(fā)送消息 %s 給 %s", message, toUser));
}
@Override
public Object watch(String messageId) {
//根據(jù)消息編碼獲取消息的狀態(tài)都办,組成監(jiān)控的數(shù)據(jù)對象嫡锌,然后返回
return null;
}
}
實現(xiàn)發(fā)送特急消息
特急消息不需要查看處理進(jìn)程虑稼,只有沒有完成,就直接催促势木,也就是說蛛倦,對于特急消息,在普通消息的處理基礎(chǔ)上啦桌,需要添加催促的功能溯壶。
觀察上面的系統(tǒng)結(jié)構(gòu)圖,會發(fā)現(xiàn)一個很明顯的問題甫男,那就是通過這種繼承的方式來拓展消息處理且改,會非常不方便。實現(xiàn)加急消息處理的時候板驳,必須實現(xiàn)系統(tǒng)內(nèi)短消息和郵件兩種處理方式又跛,因為業(yè)務(wù)處理可能不同,在實現(xiàn)特急消息處理的時候若治,又必須實現(xiàn)系統(tǒng)內(nèi)短消息和郵件兩種處理方式慨蓝。這意味著,以后每次拓展一次消息處理端幼,都必須要實現(xiàn)這兩種處理方式礼烈,這還不算完,如果要添加新的處理方式呢婆跑?
添加發(fā)送手機(jī)消息的處理方式
如果要添加一種新的發(fā)送消息的方式此熬,是需要在每一種抽象的具體實現(xiàn)中,都添加發(fā)送手機(jī)消息的處理的滑进。也就是說犀忱,發(fā)送普通消息、加急消息郊供、特急消息的處理峡碉,都可以通過手機(jī)來發(fā)送。
采用通過繼承來擴(kuò)展的實現(xiàn)方式驮审,有個明顯的缺點鲫寄,擴(kuò)展消息的種類不太容易吉执。不同種類的消息具有不同的業(yè)務(wù),也就是有不同的實現(xiàn)地来。在這種情況下戳玫,每一種類的消息,需要實現(xiàn)所有不同的消息發(fā)送方式未斑。更可怕的是咕宿,如果要新加入一種消息的發(fā)送方式,那么會要求所有的消息種類都有加入這種新的發(fā)送方式的實現(xiàn)蜡秽。
那么究竟該如何才能既實現(xiàn)功能府阀,又可以靈活的拓展呢?
使用橋梁模式來解決問題
根據(jù)業(yè)務(wù)的功能要求芽突,業(yè)務(wù)的變化具有兩個維度试浙,一個維度是抽象的消息,包括普通消息寞蚌、加急消息和特急消息田巴,這幾個抽象的消息本身就具有一定的關(guān)系,加急消息和特急消息會拓展普通消息挟秤;另一個維度是在具體的消息發(fā)送方式上壹哺,包括系統(tǒng)內(nèi)短消息、郵件短消息和手機(jī)短消息艘刚,這幾個方式是平等的管宵,可被切換的方式。
![消息發(fā)送維度解析](http://chuantu.biz/t5/137/1500211991x3683574917.png)
示例代碼
實現(xiàn)消息發(fā)送的?統(tǒng)一接口
public interface MessageImplementor {
/**
* 發(fā)送消息方法
* @param message 要發(fā)送消息的內(nèi)容
* @param toUser 接收人
*/
void send(String message, String toUser);
}
抽象消息類
public abstract class AbstractMessage {
/**
* 持有一個實現(xiàn)部分的對象
*/
MessageImplementor implementor;
/**
* 構(gòu)造方法昔脯,傳入實現(xiàn)部分的對象
* @param implementor 實現(xiàn)部分的對象
*/
public AbstractMessage(MessageImplementor implementor) {
this.implementor = implementor;
}
/**
* 發(fā)送消息啄糙,委派給實現(xiàn)部分的方法
* @param message 要發(fā)送的消息
* @param toUser 接收人
*/
public void sendMessage(String message, String toUser) {
this.implementor.send(message, toUser);
}
}
普通消息類
public class CommonMessage extends AbstractMessage {
/**
* 構(gòu)造方法,傳入實現(xiàn)部分的對象
*
* @param implementor 實現(xiàn)部分的對象
*/
public CommonMessage(MessageImplementor implementor) {
super(implementor);
}
@Override
public void sendMessage(String message, String toUser) {
//對于普通消息云稚,直接調(diào)用父類方法隧饼,發(fā)送消息即可
super.sendMessage(message, toUser);
}
}
加急消息類
public class UrgencyMessage extends AbstractMessage {
/**
* 構(gòu)造方法,傳入實現(xiàn)部分的對象
*
* @param implementor 實現(xiàn)部分的對象
*/
public UrgencyMessage(MessageImplementor implementor) {
super(implementor);
}
@Override
public void sendMessage(String message, String toUser) {
message = "加急:" + message;
super.sendMessage(message, toUser);
}
/**
* 擴(kuò)展它自己的功能静陈,監(jiān)控某個消息的處理狀態(tài)
* @param messageId 消息編碼
* @return 監(jiān)控到的消息的處理狀態(tài)
*/
public Object watch(String messageId) {
//根據(jù)給出的消息編碼查詢消息的處理狀態(tài)燕雁,組織成監(jiān)控的處理狀態(tài),然后返回鲸拥。
return null;
}
}
系統(tǒng)內(nèi)短消息的實現(xiàn)類
public class MessageSMS implements MessageImplementor {
@Override
public void send(String message, String toUser) {
System.out.println(String.format("使用系統(tǒng)內(nèi)部短消息的方法拐格,發(fā)送消息 %s 給 %s", message, toUser));
}
}
郵件短消息的實現(xiàn)類
public class MessageEmail implements MessageImplementor {
@Override
public void send(String message, String toUser) {
System.out.println(String.format("使用郵件短消息的方法,發(fā)送消息 %s 給 %s", message, toUser));
}
}
客戶端類
public class Client {
public static void main (String[] args) {
MessageImplementor implementor = new MessageSMS();
AbstractMessage abstractMessage = new CommonMessage(implementor);
abstractMessage.sendMessage("加班申請速批", "陳總");
implementor = new MessageEmail();
abstractMessage = new UrgencyMessage(implementor);
abstractMessage.sendMessage("加班申請速批", "陳總");
}
}
通過上面的例子會發(fā)現(xiàn)刑赶,采用橋梁模式來實現(xiàn)捏浊,抽象部分和實現(xiàn)部分分離開了,可以相互獨立的變化撞叨,而不會相互影響金踪。因此在抽象部分增加新的消息處理(特急消息)浊洞,對發(fā)送消息的實現(xiàn)部分是沒有影響的;反過來增加發(fā)送消息的方式(手機(jī)短消息)胡岔,對消息處理部分也是沒有影響的法希。
橋梁模式的優(yōu)點
-
分離抽象部分和實現(xiàn)部分
橋梁模式分離了抽象部分和實現(xiàn)部分,從而極大的提高了系統(tǒng)的靈活性靶瘸。讓抽象部分和實現(xiàn)部分獨立出來苫亦,分別定義接口,這有助于對系統(tǒng)進(jìn)行分層怨咪,從而產(chǎn)生更好的結(jié)構(gòu)化的系統(tǒng)屋剑。 -
更好的擴(kuò)展性
橋梁模式使得抽象部分和實現(xiàn)部分可以分別獨立的擴(kuò)展,而不會相互影響惊暴,從而大大提高了系統(tǒng)的可擴(kuò)展性饼丘。
橋梁模式在Java中的使用
橋梁模式在Java應(yīng)用中一個非常典型的例子就是JDBC驅(qū)動器。JDBC為所有的關(guān)系型數(shù)據(jù)庫提供一個通用的界面辽话。一個應(yīng)用系統(tǒng)動態(tài)的選擇一個合適的驅(qū)動器,然后通過驅(qū)動器向數(shù)據(jù)庫引擎發(fā)出指令卫病。這個過程就是將抽象角色的行為委派給實現(xiàn)角色的過程油啤。
抽象角色可以針對任何數(shù)據(jù)庫引擎發(fā)出查詢指令,因為抽象角色并不直接?與數(shù)據(jù)庫引擎打交道蟀苛,JDBC驅(qū)動器負(fù)責(zé)這個底層的工作益咬。由于JDBC驅(qū)動器的存在,應(yīng)用系統(tǒng)可以不依賴于數(shù)據(jù)庫引擎的細(xì)節(jié)而獨立的演化帜平;同時數(shù)據(jù)庫引擎也可以獨立?于應(yīng)用系統(tǒng)的細(xì)節(jié)而獨立的演化幽告。兩個獨立的等級結(jié)構(gòu)圖如下圖所示,左邊是JDBC API的等級結(jié)構(gòu)裆甩,右邊是JDBC驅(qū)動器的等級結(jié)構(gòu)冗锁,應(yīng)用程序是建立在JDBC API的基礎(chǔ)之上的。
應(yīng)用系統(tǒng)作為一個等級結(jié)構(gòu)嗤栓,與JDBC驅(qū)動器這個等級結(jié)構(gòu)是相對獨立的冻河,它們之間沒有靜態(tài)的強(qiáng)關(guān)聯(lián)。應(yīng)用系統(tǒng)通過委派與JDBC驅(qū)動器相互作用茉帅,這是一個橋梁模式的例子叨叙。
JDBC這種架構(gòu),把抽象部分和具體實現(xiàn)部分分離開來堪澎,從而使得抽象部分和具體實現(xiàn)部分都可以獨立的擴(kuò)展擂错。對于應(yīng)用程序而言,之遙選用不同的驅(qū)動樱蛤,就可以讓程序操作不同的數(shù)據(jù)庫钮呀,而無需更改應(yīng)用程序桃犬,從而實現(xiàn)在不同的數(shù)據(jù)庫上移植;對于驅(qū)動程序而言行楞,為數(shù)據(jù)庫實現(xiàn)不同的驅(qū)動程序攒暇,并不會影響應(yīng)用程序。