概念
橋梁模式是對(duì)象的結(jié)構(gòu)模式。又稱為柄體(Handle and Body)模式或接口(Interface)模式扯罐。橋梁模式的用意是“將抽象化(Abstraction)與實(shí)現(xiàn)化(Implementation)脫耦,使得二者可以獨(dú)立地變化”
橋接模式的用意
橋梁模式雖然不是一個(gè)使用頻率很高的模式,但是熟悉這個(gè)模式對(duì)于理解面向?qū)ο蟮脑O(shè)計(jì)原則,包括“開-閉”原則以及組合/聚合復(fù)用原則都很有幫助偷仿。理解好這兩個(gè)原則,有助于形成正確的設(shè)計(jì)思想和培養(yǎng)良好的設(shè)計(jì)風(fēng)格宵蕉。
橋梁模式的用意是“將抽象化(Abstraction)與實(shí)現(xiàn)化(Implementation)脫耦酝静,使得二者可以獨(dú)立地變化”。這句話很短羡玛,但是第一次讀到這句話的人很可能都會(huì)思考良久而不解其意形入。
這句話有三個(gè)關(guān)鍵詞,也就是抽象化缝左、實(shí)現(xiàn)化和脫耦亿遂。理解這三個(gè)詞所代表的概念是理解橋梁模式用意的關(guān)鍵浓若。
1. 抽象化
從眾多的事物中抽取出共同的、本質(zhì)性的特征蛇数,而舍棄其非本質(zhì)的特征挪钓,就是抽象化。例如蘋果耳舅、香蕉碌上、生梨、 桃子等浦徊,它們共同的特性就是水果馏予。得出水果概念的過程,就是一個(gè)抽象化的過程盔性。要抽象霞丧,就必須進(jìn)行比較,沒有比較就無法找到在本質(zhì)上共同的部分冕香。共同特征是指那些能把一類事物與他類事物區(qū)分開來的特征蛹尝,這些具有區(qū)分作用的特征又稱本質(zhì)特征。因此抽取事物的共同特征就是抽取事物的本質(zhì)特征悉尾,舍棄非本質(zhì)的特征突那。 所以抽象化的過程也是一個(gè)裁剪的過程。在抽象時(shí)构眯,同與不同愕难,決定于從什么角度上來抽象。抽象的角度取決于分析問題的目的惫霸。
通常情況下务漩,一組對(duì)象如果具有相同的特征,那么它們就可以通過一個(gè)共同的類來描述它褪。如果一些類具有相同的特征,往往可以通過一個(gè)共同的抽象類來描述翘悉。
**2. 實(shí)現(xiàn)化
抽象化給出的具體實(shí)現(xiàn)茫打,就是實(shí)現(xiàn)化。
一個(gè)類的實(shí)例就是這個(gè)類的實(shí)例化妖混,一個(gè)具體子類是它的抽象超類的實(shí)例化老赤。
**3. 脫耦化
所謂耦合,就是兩個(gè)實(shí)體的行為的某種強(qiáng)關(guān)聯(lián)制市。而將它們的強(qiáng)關(guān)聯(lián)去掉抬旺,就是耦合的解脫,或稱脫耦祥楣。在這里开财,脫耦是指將抽象化和實(shí)現(xiàn)化之間的耦合解脫開汉柒,或者說是將它們之間的強(qiáng)關(guān)聯(lián)改換成弱關(guān)聯(lián)。
所謂強(qiáng)關(guān)聯(lián)责鳍,就是在編譯時(shí)期已經(jīng)確定的碾褂,無法在運(yùn)行時(shí)期動(dòng)態(tài)改變的關(guān)聯(lián);所謂弱關(guān)聯(lián)历葛,就是可以動(dòng)態(tài)地確定并且可以在運(yùn)行時(shí)期動(dòng)態(tài)地改變的關(guān)聯(lián)正塌。顯然,在Java語言中恤溶,繼承關(guān)系是強(qiáng)關(guān)聯(lián)乓诽,而聚合關(guān)系是弱關(guān)聯(lián)。
將兩個(gè)角色之間的繼承關(guān)系改為聚合關(guān)系咒程,就是將它們之間的強(qiáng)關(guān)聯(lián)改換成為弱關(guān)聯(lián)鸠天。因此,橋梁模式中的所謂脫耦孵坚,就是指在一個(gè)軟件系統(tǒng)的抽象化和實(shí)現(xiàn)化之間使用聚合關(guān)系而不是繼承關(guān)系粮宛,從而使兩者可以相對(duì)獨(dú)立地變化。這就是橋梁模式的用意卖宠。
橋梁模式的結(jié)構(gòu)
可以看出巍杈,這個(gè)系統(tǒng)含有兩個(gè)等級(jí)結(jié)構(gòu):
一、由抽象化角色和修正抽象化角色組成的抽象化等級(jí)結(jié)構(gòu)扛伍。
二筷畦、由實(shí)現(xiàn)化角色和兩個(gè)具體實(shí)現(xiàn)化角色所組成的實(shí)現(xiàn)化等級(jí)結(jié)構(gòu)。
橋梁模式所涉及的角色有:
● 抽象化(Abstraction)角色:抽象化給出的定義刺洒,并保存一個(gè)對(duì)實(shí)現(xiàn)化對(duì)象的引用鳖宾。
● 修正抽象化(RefinedAbstraction)角色:擴(kuò)展抽象化角色,改變和修正父類對(duì)抽象化的定義逆航。
● 實(shí)現(xiàn)化(Implementor)角色:這個(gè)角色給出實(shí)現(xiàn)化角色的接口鼎文,但不給出具體的實(shí)現(xiàn)。必須指出的是因俐,這個(gè)接口不一定和抽象化角色的接口定義相同拇惋,實(shí)際上,這兩個(gè)接口可以非常不一樣抹剩。實(shí)現(xiàn)化角色應(yīng)當(dāng)只給出底層操作撑帖,而抽象化角色應(yīng)當(dāng)只給出基于底層操作的更高一層的操作。
● 具體實(shí)現(xiàn)化(ConcreteImplementor)角色:這個(gè)角色給出實(shí)現(xiàn)化角色接口的具體實(shí)現(xiàn)澳眷。
抽象化角色就像是一個(gè)水杯的手柄胡嘿,而實(shí)現(xiàn)化角色和具體實(shí)現(xiàn)化角色就像是水杯的杯身。手柄控制杯身钳踊,這就是此模式別名“柄體”的來源衷敌。
對(duì)象是對(duì)行為的封裝勿侯,而行為是由方法實(shí)現(xiàn)的。在這個(gè)示意性系統(tǒng)里逢享,抽象化等級(jí)結(jié)構(gòu)中的類封裝了operation()方法罐监;而實(shí)現(xiàn)化等級(jí)結(jié)構(gòu)中的類封裝的是operationImpl()方法。當(dāng)然瞒爬,在實(shí)際的系統(tǒng)中往往會(huì)有多于一個(gè)的方法弓柱。
抽象化等級(jí)結(jié)構(gòu)中的方法通過向?qū)?yīng)的實(shí)現(xiàn)化對(duì)象的委派實(shí)現(xiàn)自己的功能,這意味著抽象化角色可以通過向不同的實(shí)現(xiàn)化對(duì)象委派侧但,來達(dá)到動(dòng)態(tài)地轉(zhuǎn)換自己的功能的目的矢空。
源代碼
/**
* 抽象化角色類,它聲明了一個(gè)方法operation()禀横,并給出了它的實(shí)現(xiàn)屁药。
* 這個(gè)實(shí)現(xiàn)是通過向?qū)崿F(xiàn)化對(duì)象的委派(調(diào)用operationImpl()方法)實(shí)現(xiàn)的。
*/
public abstract class Abstraction {
//持有一個(gè)實(shí)現(xiàn)化(Implementor)角色接口
private Implementor impl;
public Implementor getImpl() {
return impl;
}
public void setImpl(Implementor impl) {
this.impl = impl;
}
//示例方法
public void operation(){
impl.operationImpl();
}
}
/**
* 修正抽象化角色柏锄,這里可以重寫父類的operation方法施逾,
* 也可以不重寫故痊,不重寫即調(diào)用父類的operation方法
*/
public class RefinedAbstraction extends Abstraction {
//其他操作方法
public void otherOperation(){}
}
/**
* 實(shí)現(xiàn)化(Implementor)角色接口
*/
public abstract class Implementor {
/**
* 示例方法,實(shí)現(xiàn)抽象部分需要的某些具體功能
*/
public abstract void operationImpl();
}
/**
* 具體實(shí)現(xiàn)化角色
*/
public class ConcreteImplementorA extends Implementor {
@Override
public void operationImpl() {
System.out.println("實(shí)現(xiàn)A功能");
}
}
/**
* 具體實(shí)現(xiàn)化角色
*/
public class ConcreteImplementorB extends Implementor {
@Override
public void operationImpl() {
System.out.println("實(shí)現(xiàn)B功能");
}
}
/**
* 客戶端
*/
public class Client {
public static void main(String[] args) {
Abstraction abstraction = new RefinedAbstraction();
Implementor implA = new ConcreteImplementorA();
abstraction.setImpl(implA);
abstraction.operation();
System.out.println("----");
Implementor implB = new ConcreteImplementorB();
abstraction.setImpl(implB);
abstraction.operation();
}
}
一般而言,實(shí)現(xiàn)化角色中的每個(gè)方法都應(yīng)當(dāng)有一個(gè)抽象化角色中的某一個(gè)方法與之對(duì)應(yīng)轨蛤,但是反過來則不一定朝氓。換言之缸逃,抽象化角色的接口比實(shí)現(xiàn)化角色的接口寬少辣。抽象化角色除了提供與實(shí)現(xiàn)化角色相關(guān)的方法之外,還有可能提供其他的方法笤成;而實(shí)現(xiàn)化角色則往往僅為實(shí)現(xiàn)抽象化角色的相關(guān)行為而存在评架。
使用橋梁模式解決發(fā)送信息問題
根據(jù)業(yè)務(wù)的功能要求,業(yè)務(wù)的變化具有兩個(gè)維度炕泳,一個(gè)維度是抽象的消息纵诞,包括普通消息、加急消息和特急消息培遵,這幾個(gè)抽象的消息本身就具有一定的關(guān)系浙芙,加急消息和特急消息會(huì)擴(kuò)展普通消息;另一個(gè)維度是在具體的消息發(fā)送方式上荤懂,包括系統(tǒng)內(nèi)短消息、郵件和手機(jī)短消息塘砸,這幾個(gè)方式是平等的节仿,可被切換的方式。
現(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ò)展變得簡(jiǎn)單。抽象部分就是各個(gè)消息的類型所對(duì)應(yīng)的功能荐操,而實(shí)現(xiàn)部分就是各種發(fā)送消息的方式芜抒。按照橋梁模式的結(jié)構(gòu),給抽象部分和實(shí)現(xiàn)部分分別定義接口托启,然后分別實(shí)現(xiàn)它們就可以了宅倒。
源碼
/**
* 抽象化角色
*/
public abstract class AbstractMessage {
//持有發(fā)送消息的實(shí)現(xiàn)化角色接口
private MessageImplementor implementor;
public void setImplementor(MessageImplementor implementor) {
this.implementor = implementor;
}
//實(shí)際調(diào)用的是實(shí)現(xiàn)化角色的發(fā)送消息方法
public void sendMessage(String msg){
this.implementor.sendMessage(msg);
}
}
/**
* 普通消息
*/
public class SimpleMessage extends AbstractMessage {
@Override
public void sendMessage(String msg) {
//普通消息不做任何處理
super.sendMessage(msg);
}
}
/**
* 加急消息
*/
public class UrgencyMessage extends AbstractMessage {
/**
* 這里有點(diǎn)像裝飾模式,可以在
* 實(shí)際調(diào)用發(fā)送消息之前加上具體的業(yè)務(wù)邏輯
* @param msg
*/
@Override
public void sendMessage(String msg) {
msg += "(這個(gè)是加急消息)";
super.sendMessage(msg);
}
}
/**
* 實(shí)現(xiàn)消息發(fā)送的接口
*/
public interface MessageImplementor {
/**
* 發(fā)送消息
* @param msg
*/
public void sendMessage(String msg);
}
/**
* 短信消息實(shí)現(xiàn)類
*/
public class MessageSMS implements MessageImplementor {
@Override
public void sendMessage(String msg) {
System.out.println("發(fā)送短信:" + msg);
}
}
/**
* 郵件消息實(shí)現(xiàn)類
*/
public class MessageEmail implements MessageImplementor {
@Override
public void sendMessage(String msg) {
System.out.println("郵件信息:" + msg);
}
}
/**
* 客戶端
*/
public class Client {
public static void main(String[] args) {
//發(fā)送短信
MessageImplementor sms = new MessageSMS();
//發(fā)送郵件
MessageImplementor email = new MessageEmail();
//普通消息
AbstractMessage simpleMessage = new SimpleMessage();
//加急消息
AbstractMessage urgencyMessage = new UrgencyMessage();
simpleMessage.setImplementor(sms);
simpleMessage.sendMessage("test");
simpleMessage.setImplementor(email);
simpleMessage.sendMessage("test");
urgencyMessage.setImplementor(sms);
urgencyMessage.sendMessage("test");
urgencyMessage.setImplementor(email);
urgencyMessage.sendMessage("test");
}
}
觀察上面的例子會(huì)發(fā)現(xiàn)屯耸,采用橋梁模式來實(shí)現(xiàn)拐迁,抽象部分和實(shí)現(xiàn)部分分離開了,可以相互獨(dú)立的變化疗绣,而不會(huì)相互影響线召。因此在抽象部分添加新的消息處理(特急消息),對(duì)發(fā)送消息的實(shí)現(xiàn)部分是沒有影響的持痰;反過來增加發(fā)送消息的方式(手機(jī)短消息)灶搜,對(duì)消息處理部分也是沒有影響的。
橋梁模式的優(yōu)點(diǎn)
1. 分離抽象和實(shí)現(xiàn)部分
橋梁模式分離了抽象部分和實(shí)現(xiàn)部分工窍,從而極大地提供了系統(tǒng)的靈活性割卖。讓抽象部分和實(shí)現(xiàn)部分獨(dú)立出來,分別定義接口患雏,這有助于對(duì)系統(tǒng)進(jìn)行分層鹏溯,從而產(chǎn)生更好的結(jié)構(gòu)化的系統(tǒng)。
2. 更好的擴(kuò)展性
橋梁模式使得抽象部分和實(shí)現(xiàn)部分可以分別獨(dú)立地?cái)U(kuò)展淹仑,而不會(huì)相互影響丙挽,從而大大提高了系統(tǒng)的可擴(kuò)展性。
橋梁模式在Java中的使用
橋梁模式在Java應(yīng)用中的一個(gè)非常典型的例子就是JDBC驅(qū)動(dòng)器匀借。JDBC為所有的關(guān)系型數(shù)據(jù)庫提供一個(gè)通用的界面颜阐。一個(gè)應(yīng)用系統(tǒng)動(dòng)態(tài)地選擇一個(gè)合適的驅(qū)動(dòng)器,然后通過驅(qū)動(dòng)器向數(shù)據(jù)庫引擎發(fā)出指令吓肋。這個(gè)過程就是將抽象角色的行為委派給實(shí)現(xiàn)角色的過程凳怨。
抽象角色可以針對(duì)任何數(shù)據(jù)庫引擎發(fā)出查詢指令,因?yàn)槌橄蠼巧⒉恢苯优c數(shù)據(jù)庫引擎打交道,JDBC驅(qū)動(dòng)器負(fù)責(zé)這個(gè)底層的工作肤舞。由于JDBC驅(qū)動(dòng)器的存在紫新,應(yīng)用系統(tǒng)可以不依賴于數(shù)據(jù)庫引擎的細(xì)節(jié)而獨(dú)立地演化;同時(shí)數(shù)據(jù)庫引擎也可以獨(dú)立于應(yīng)用系統(tǒng)的細(xì)節(jié)而獨(dú)立的演化李剖。兩個(gè)獨(dú)立的等級(jí)結(jié)構(gòu)如下圖所示芒率,左邊是JDBC API的等級(jí)結(jié)構(gòu),右邊是JDBC驅(qū)動(dòng)器的等級(jí)結(jié)構(gòu)篙顺。應(yīng)用程序是建立在JDBC API的基礎(chǔ)之上的偶芍。
應(yīng)用系統(tǒng)作為一個(gè)等級(jí)結(jié)構(gòu),與JDBC驅(qū)動(dòng)器這個(gè)等級(jí)結(jié)構(gòu)是相對(duì)獨(dú)立的慰安,它們之間沒有靜態(tài)的強(qiáng)關(guān)聯(lián)腋寨。應(yīng)用系統(tǒng)通過委派與JDBC驅(qū)動(dòng)器相互作用,這是一個(gè)橋梁模式的例子化焕。
JDBC的這種架構(gòu)萄窜,把抽象部分和具體部分分離開來,從而使得抽象部分和具體部分都可以獨(dú)立地?cái)U(kuò)展撒桨。對(duì)于應(yīng)用程序而言查刻,只要選用不同的驅(qū)動(dòng),就可以讓程序操作不同的數(shù)據(jù)庫凤类,而無需更改應(yīng)用程序穗泵,從而實(shí)現(xiàn)在不同的數(shù)據(jù)庫上移植;對(duì)于驅(qū)動(dòng)程序而言谜疤,為數(shù)據(jù)庫實(shí)現(xiàn)不同的驅(qū)動(dòng)程序佃延,并不會(huì)影響應(yīng)用程序。