《JAVA与模式》之桥梁模式
在閻宏博士的《JAVA與模式》一書中開頭是這樣描述橋梁(Bridge)模式的:
橋梁模式是對象的結(jié)構(gòu)模式。又稱為柄體(Handle and Body)模式或接口(Interface)模式。橋梁模式的用意是“將抽象化(Abstraction)與實(shí)現(xiàn)化(Implementation)脫耦,使得二者可以獨(dú)立地變化”。
橋梁模式的用意
橋梁模式雖然不是一個(gè)使用頻率很高的模式,但是熟悉這個(gè)模式對于理解面向?qū)ο蟮脑O(shè)計(jì)原則,包括“開-閉”原則以及組合/聚合復(fù)用原則都很有幫助。理解好這兩個(gè)原則,有助于形成正確的設(shè)計(jì)思想和培養(yǎng)良好的設(shè)計(jì)風(fēng)格。
橋梁模式的用意是“將抽象化(Abstraction)與實(shí)現(xiàn)化(Implementation)脫耦,使得二者可以獨(dú)立地變化”。這句話很短,但是第一次讀到這句話的人很可能都會思考良久而不解其意。
這句話有三個(gè)關(guān)鍵詞,也就是抽象化、實(shí)現(xiàn)化和脫耦。理解這三個(gè)詞所代表的概念是理解橋梁模式用意的關(guān)鍵。
- 抽象化
從眾多的事物中抽取出共同的、本質(zhì)性的特征,而舍棄其非本質(zhì)的特征,就是抽象化。例如蘋果、香蕉、生梨、 桃子等,它們共同的特性就是水果。得出水果概念的過程,就是一個(gè)抽象化的過程。要抽象,就必須進(jìn)行比較,沒有比較就無法找到在本質(zhì)上共同的部分。共同特征是指那些能把一類事物與他類事物區(qū)分開來的特征,這些具有區(qū)分作用的特征又稱本質(zhì)特征。因此抽取事物的共同特征就是抽取事物的本質(zhì)特征,舍棄非本質(zhì)的特征。 所以抽象化的過程也是一個(gè)裁剪的過程。在抽象時(shí),同與不同,決定于從什么角度上來抽象。抽象的角度取決于分析問題的目的。
通常情況下,一組對象如果具有相同的特征,那么它們就可以通過一個(gè)共同的類來描述。如果一些類具有相同的特征,往往可以通過一個(gè)共同的抽象類來描述。
- 實(shí)現(xiàn)化
抽象化給出的具體實(shí)現(xiàn),就是實(shí)現(xiàn)化。
一個(gè)類的實(shí)例就是這個(gè)類的實(shí)例化,一個(gè)具體子類是它的抽象超類的實(shí)例化。
- 脫耦
所謂耦合,就是兩個(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í)期動態(tài)改變的關(guān)聯(lián);所謂弱關(guān)聯(lián),就是可以動態(tài)地確定并且可以在運(yùn)行時(shí)期動態(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)系,從而使兩者可以相對獨(dú)立地變化。這就是橋梁模式的用意。
橋梁模式的結(jié)構(gòu)
下圖所示就是一個(gè)實(shí)現(xiàn)了橋梁模式的示意性系統(tǒng)的結(jié)構(gòu)圖:
可以看出,這個(gè)系統(tǒng)含有兩個(gè)等級結(jié)構(gòu):
一、由抽象化角色和修正抽象化角色組成的抽象化等級結(jié)構(gòu)。
二、由實(shí)現(xiàn)化角色和兩個(gè)具體實(shí)現(xiàn)化角色所組成的實(shí)現(xiàn)化等級結(jié)構(gòu)。
橋梁模式所涉及的角色有:
● 抽象化(Abstraction)角色:抽象化給出的定義,并保存一個(gè)對實(shí)現(xiàn)化對象的引用。
● 修正抽象化(RefinedAbstraction)角色:擴(kuò)展抽象化角色,改變和修正父類對抽象化的定義。
● 實(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)化角色就像是水杯的杯身。手柄控制杯身,這就是此模式別名“柄體”的來源。
對象是對行為的封裝,而行為是由方法實(shí)現(xiàn)的。在這個(gè)示意性系統(tǒng)里,抽象化等級結(jié)構(gòu)中的類封裝了operation()方法;而實(shí)現(xiàn)化等級結(jié)構(gòu)中的類封裝的是operationImpl()方法。當(dāng)然,在實(shí)際的系統(tǒng)中往往會有多于一個(gè)的方法。
抽象化等級結(jié)構(gòu)中的方法通過向?qū)?yīng)的實(shí)現(xiàn)化對象的委派實(shí)現(xiàn)自己的功能,這意味著抽象化角色可以通過向不同的實(shí)現(xiàn)化對象委派,來達(dá)到動態(tài)地轉(zhuǎn)換自己的功能的目的。
源代碼
抽象化角色類,它聲明了一個(gè)方法operation(),并給出了它的實(shí)現(xiàn)。這個(gè)實(shí)現(xiàn)是通過向?qū)崿F(xiàn)化對象的委派(調(diào)用operationImpl()方法)實(shí)現(xiàn)的。
public abstract class Abstraction {protected Implementor impl; public Abstraction(Implementor impl){ this.impl = impl; } //示例方法 public void operation(){ impl.operationImpl(); } }修正抽象化角色
public class RefinedAbstraction extends Abstraction {public RefinedAbstraction(Implementor impl) { super(impl); } //其他的操作方法 public void otherOperation(){ } }實(shí)現(xiàn)化角色
public abstract class Implementor {/** * 示例方法,實(shí)現(xiàn)抽象部分需要的某些具體功能 */ public abstract void operationImpl(); }具體實(shí)現(xiàn)化角色
public class ConcreteImplementorA extends Implementor {@Overridepublic void operationImpl() { //具體操作 } } public class ConcreteImplementorB extends Implementor {@Overridepublic void operationImpl() { //具體操作 } }一般而言,實(shí)現(xiàn)化角色中的每個(gè)方法都應(yīng)當(dāng)有一個(gè)抽象化角色中的某一個(gè)方法與之對應(yīng),但是反過來則不一定。換言之,抽象化角色的接口比實(shí)現(xiàn)化角色的接口寬。抽象化角色除了提供與實(shí)現(xiàn)化角色相關(guān)的方法之外,還有可能提供其他的方法;而實(shí)現(xiàn)化角色則往往僅為實(shí)現(xiàn)抽象化角色的相關(guān)行為而存在。
?
使用場景
考慮這樣一個(gè)實(shí)際的業(yè)務(wù)功能:發(fā)送提示消息。基本上所有帶業(yè)務(wù)流程處理的系統(tǒng)都會有這樣的功能,比如OA上有尚未處理完畢的文件,需要發(fā)送一條消息提示他。
從業(yè)務(wù)上看,消息又分成普通消息、加急消息和特急消息多種,不同的消息類型,業(yè)務(wù)功能處理是不一樣的,比如加急消息是在消息上添加加急,而特急消息除了添加特急外,還會做一條催促的記錄,多久不完成會繼續(xù)催促;從發(fā)送消息的手段上看,又有系統(tǒng)內(nèi)短消息、手機(jī)短信息、郵件等。
不使用模式的解決方案
?
實(shí)現(xiàn)發(fā)送普通消息
先考慮實(shí)現(xiàn)一個(gè)簡單點(diǎn)的版本,比如,消息只是實(shí)現(xiàn)發(fā)送普通消息,發(fā)送的方式只實(shí)現(xiàn)系統(tǒng)內(nèi)短消息和郵件。其他的功能,等這個(gè)版本完成后,再繼續(xù)添加。
源代碼
消息的統(tǒng)一接口
public interface Message {/*** 發(fā)送消息* @param message 要發(fā)送消息的內(nèi)容 * @param toUser 消息的接受者 */ public void send(String message , String toUser); }系統(tǒng)內(nèi)短消息示例類
public class CommonMessageSMS implements Message {@Overridepublic void send(String message, String toUser) { System.out.println("使用系統(tǒng)內(nèi)短消息的方法,發(fā)送消息'"+message+"'給"+toUser); } }郵件消息示例類
public class CommonMessageEmail implements Message{@Overridepublic void send(String message, String toUser) { System.out.println("使用郵件短消息的方法,發(fā)送消息'"+message+"'給"+toUser); } }實(shí)現(xiàn)發(fā)送加急消息
發(fā)送加急消息同樣有兩種方式,系統(tǒng)內(nèi)短消息和郵件方式。但是加急消息的實(shí)現(xiàn)不同于普通消息,加急消息會自動在消息上添加加急,然后在再發(fā)送消息;另外加急消息會提供監(jiān)控的方法,讓客戶端可以隨時(shí)通過這個(gè)方法來了解對于加急消息的處理進(jìn)度。比如,相應(yīng)的人員是否接收到這個(gè)信息,相應(yīng)的處理工作是否已經(jīng)展開。因此加急消息需要擴(kuò)展出一個(gè)新的接口,除了基本的發(fā)送消息的功能,還需要添加監(jiān)控功能。
源代碼
加急消息的接口
public interface UrgencyMessage extends Message {/** * 監(jiān)控指定消息的處理過程 * @param messageId 被監(jiān)控的消息編號 * @return 監(jiān)控到的消息的處理狀態(tài) */ public Object watch(String messageId); }系統(tǒng)內(nèi)加急短消息示例類
public class UrgencyMessageSMS implements UrgencyMessage {@Overridepublic Object watch(String messageId) { // 根據(jù)消息id獲取消息的狀態(tài),組織成監(jiān)控的數(shù)據(jù)對象,然后返回 return null; } @Override public void send(String message, String toUser) { message = "加急:" + message; System.out.println("使用系統(tǒng)內(nèi)短消息的方法,發(fā)送消息'"+message+"'給"+toUser); } }郵件加急短消息示例類
public class UrgencyMessageEmail implements UrgencyMessage {@Overridepublic Object watch(String messageId) { // 根據(jù)消息id獲取消息的狀態(tài),組織成監(jiān)控的數(shù)據(jù)對象,然后返回 return null; } @Override public void send(String message, String toUser) { message = "加急:" + message; System.out.println("使用郵件短消息的方法,發(fā)送消息'"+message+"'給"+toUser); } }實(shí)現(xiàn)發(fā)送特急消息
特急消息不需要查看處理進(jìn)程,只有沒有完成,就直接催促,也就是說,對于特急消息,在普通消息的處理基礎(chǔ)上,需要添加催促的功能。
觀察上面的系統(tǒng)結(jié)構(gòu)圖,會發(fā)現(xiàn)一個(gè)很明顯的問題,那就是通過這種繼承的方式來擴(kuò)展消息處理,會非常不方便。實(shí)現(xiàn)加急消息處理的時(shí)候,必須實(shí)現(xiàn)系統(tǒng)內(nèi)短消息和郵件兩種處理方式,因?yàn)闃I(yè)務(wù)處理可能不同,在實(shí)現(xiàn)特急消息處理的時(shí)候,又必須實(shí)現(xiàn)系統(tǒng)內(nèi)短信息和郵件兩種處理方式。這意味著,以后每次擴(kuò)展一下消息處理,都必須要實(shí)現(xiàn)這兩種處理方式,這還不算完,如果要添加新的實(shí)現(xiàn)方式呢?
添加發(fā)送手機(jī)消息的處理方式
如果要添加一種新的發(fā)送消息的方式,是需要在每一種抽象的具體實(shí)現(xiàn)中,都添加發(fā)送手機(jī)消息的處理的。也就是說,發(fā)送普通消息、加急消息和特急消息的處理,都可以通過手機(jī)來發(fā)送。
采用通過繼承來擴(kuò)展的實(shí)現(xiàn)方式,有個(gè)明顯的缺點(diǎn),擴(kuò)展消息的種類不太容易。不同種類的消息具有不同的業(yè)務(wù),也就是有不同的實(shí)現(xiàn),在這種情況下,每一種類的消息,需要實(shí)現(xiàn)所有不同的消息發(fā)送方式。更可怕的是,如果要新加入一種消息的發(fā)送方式,那么會要求所有的消息種類都有加入這種新的發(fā)送方式的實(shí)現(xiàn)。
那么究竟該如何才能既實(shí)現(xiàn)功能,又可以靈活地?cái)U(kuò)展呢?
使用橋梁模式來解決問題
根據(jù)業(yè)務(wù)的功能要求,業(yè)務(wù)的變化具有兩個(gè)維度,一個(gè)維度是抽象的消息,包括普通消息、加急消息和特急消息,這幾個(gè)抽象的消息本身就具有一定的關(guān)系,加急消息和特急消息會擴(kuò)展普通消息;另一個(gè)維度是在具體的消息發(fā)送方式上,包括系統(tǒng)內(nèi)短消息、郵件和手機(jī)短消息,這幾個(gè)方式是平等的,可被切換的方式。
現(xiàn)在出現(xiàn)問題的根本原因,就在于消息的抽象和實(shí)現(xiàn)是混雜在一起的,這就導(dǎo)致了一個(gè)緯度的變化會引起另一個(gè)緯度進(jìn)行相應(yīng)的變化,從而使得程序擴(kuò)展起來非常困難。
要想解決這個(gè)問題,就必須把這兩個(gè)緯度分開,也就是將抽象部分和實(shí)現(xiàn)部分分開,讓它們相互獨(dú)立,這樣就可以實(shí)現(xiàn)獨(dú)立的變化,使擴(kuò)展變得簡單。抽象部分就是各個(gè)消息的類型所對應(yīng)的功能,而實(shí)現(xiàn)部分就是各種發(fā)送消息的方式。按照橋梁模式的結(jié)構(gòu),給抽象部分和實(shí)現(xiàn)部分分別定義接口,然后分別實(shí)現(xiàn)它們就可以了。
源代碼
抽象消息類
public abstract class AbstractMessage {//持有一個(gè)實(shí)現(xiàn)部分的對象 MessageImplementor impl; /** * 構(gòu)造方法,傳入實(shí)現(xiàn)部分的對象 * @param impl 實(shí)現(xiàn)部分的對象 */ public AbstractMessage(MessageImplementor impl){ this.impl = impl; } /** * 發(fā)送消息,委派給實(shí)現(xiàn)部分的方法 * @param message 要發(fā)送消息的內(nèi)容 * @param toUser 消息的接受者 */ public void sendMessage(String message , String toUser){ this.impl.send(message, toUser); } }?
普通消息類
public class CommonMessage extends AbstractMessage {public CommonMessage(MessageImplementor impl) { super(impl); } @Override public void sendMessage(String message, String toUser) { // 對于普通消息,直接調(diào)用父類方法,發(fā)送消息即可 super.sendMessage(message, toUser); } }加急消息類
public class UrgencyMessage extends AbstractMessage {public UrgencyMessage(MessageImplementor impl) { super(impl); } @Override public void sendMessage(String message, String toUser) { message = "加急:" + message; super.sendMessage(message, toUser); } /** * 擴(kuò)展自己的新功能,監(jiān)控某消息的處理狀態(tài) * @param messageId 被監(jiān)控的消息編號 * @return 監(jiān)控到的消息的處理狀態(tài) */ public Object watch(String messageId) { // 根據(jù)消息id獲取消息的狀態(tài),組織成監(jiān)控的數(shù)據(jù)對象,然后返回 return null; } }實(shí)現(xiàn)發(fā)送消息的統(tǒng)一接口
public interface MessageImplementor {/*** 發(fā)送消息* @param message 要發(fā)送消息的內(nèi)容 * @param toUser 消息的接受者 */ public void send(String message , String toUser); }系統(tǒng)內(nèi)短消息的實(shí)現(xiàn)類
public class MessageSMS implements MessageImplementor {@Overridepublic void send(String message, String toUser) { System.out.println("使用系統(tǒng)內(nèi)短消息的方法,發(fā)送消息'"+message+"'給"+toUser); } }郵件短消息的實(shí)現(xiàn)類
public class MessageEmail implements MessageImplementor {@Overridepublic void send(String message, String toUser) { System.out.println("使用郵件短消息的方法,發(fā)送消息'"+message+"'給"+toUser); } }客戶端類
public class Client {public static void main(String[] args) { //創(chuàng)建具體的實(shí)現(xiàn)對象 MessageImplementor impl = new MessageSMS(); //創(chuàng)建普通消息對象 AbstractMessage message = new CommonMessage(impl); message.sendMessage("加班申請速批","李總"); //將實(shí)現(xiàn)方式切換成郵件,再次發(fā)送 impl = new MessageEmail(); //創(chuàng)建加急消息對象 message = new UrgencyMessage(impl); message.sendMessage("加班申請速批","李總"); } }觀察上面的例子會發(fā)現(xiàn),采用橋梁模式來實(shí)現(xiàn),抽象部分和實(shí)現(xiàn)部分分離開了,可以相互獨(dú)立的變化,而不會相互影響。因此在抽象部分添加新的消息處理(特急消息),對發(fā)送消息的實(shí)現(xiàn)部分是沒有影響的;反過來增加發(fā)送消息的方式(手機(jī)短消息),對消息處理部分也是沒有影響的。
橋梁模式的優(yōu)點(diǎn)
● 分離抽象和實(shí)現(xiàn)部分
橋梁模式分離了抽象部分和實(shí)現(xiàn)部分,從而極大地提供了系統(tǒng)的靈活性。讓抽象部分和實(shí)現(xiàn)部分獨(dú)立出來,分別定義接口,這有助于對系統(tǒng)進(jìn)行分層,從而產(chǎn)生更好的結(jié)構(gòu)化的系統(tǒng)。
?
● 更好的擴(kuò)展性
橋梁模式使得抽象部分和實(shí)現(xiàn)部分可以分別獨(dú)立地?cái)U(kuò)展,而不會相互影響,從而大大提高了系統(tǒng)的可擴(kuò)展性。
?
橋梁模式在Java中的使用
橋梁模式在Java應(yīng)用中的一個(gè)非常典型的例子就是JDBC驅(qū)動器。JDBC為所有的關(guān)系型數(shù)據(jù)庫提供一個(gè)通用的界面。一個(gè)應(yīng)用系統(tǒng)動態(tài)地選擇一個(gè)合適的驅(qū)動器,然后通過驅(qū)動器向數(shù)據(jù)庫引擎發(fā)出指令。這個(gè)過程就是將抽象角色的行為委派給實(shí)現(xiàn)角色的過程。
抽象角色可以針對任何數(shù)據(jù)庫引擎發(fā)出查詢指令,因?yàn)槌橄蠼巧⒉恢苯优c數(shù)據(jù)庫引擎打交道,JDBC驅(qū)動器負(fù)責(zé)這個(gè)底層的工作。由于JDBC驅(qū)動器的存在,應(yīng)用系統(tǒng)可以不依賴于數(shù)據(jù)庫引擎的細(xì)節(jié)而獨(dú)立地演化;同時(shí)數(shù)據(jù)庫引擎也可以獨(dú)立于應(yīng)用系統(tǒng)的細(xì)節(jié)而獨(dú)立的演化。兩個(gè)獨(dú)立的等級結(jié)構(gòu)如下圖所示,左邊是JDBC API的等級結(jié)構(gòu),右邊是JDBC驅(qū)動器的等級結(jié)構(gòu)。應(yīng)用程序是建立在JDBC API的基礎(chǔ)之上的。
應(yīng)用系統(tǒng)作為一個(gè)等級結(jié)構(gòu),與JDBC驅(qū)動器這個(gè)等級結(jié)構(gòu)是相對獨(dú)立的,它們之間沒有靜態(tài)的強(qiáng)關(guān)聯(lián)。應(yīng)用系統(tǒng)通過委派與JDBC驅(qū)動器相互作用,這是一個(gè)橋梁模式的例子。
JDBC的這種架構(gòu),把抽象部分和具體部分分離開來,從而使得抽象部分和具體部分都可以獨(dú)立地?cái)U(kuò)展。對于應(yīng)用程序而言,只要選用不同的驅(qū)動,就可以讓程序操作不同的數(shù)據(jù)庫,而無需更改應(yīng)用程序,從而實(shí)現(xiàn)在不同的數(shù)據(jù)庫上移植;對于驅(qū)動程序而言,為數(shù)據(jù)庫實(shí)現(xiàn)不同的驅(qū)動程序,并不會影響應(yīng)用程序。
轉(zhuǎn)載于:https://www.cnblogs.com/tuojunjie/p/7450264.html
總結(jié)
以上是生活随笔為你收集整理的《JAVA与模式》之桥梁模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Mysql异常问题排查与处理——mysq
- 下一篇: 光大信用卡心e金怎么开通?没资格怎么办?