本文參考:
http://www.oschina.net/question/1436074_140456
http://design-patterns.readthedocs.io/zh_CN/latest/structural_patterns/bridge.html
一、模式動機
設想如果要繪制矩形、圓形、橢圓、正方形,我們至少需要4個形狀類,但是如果繪制的圖形需要具有不同的顏色,如紅色、綠色、藍色等,此時至少有如下兩種設計方案:
- 第一種設計方案是為每一種形狀都提供一套各種顏色的版本。
- 第二種設計方案是根據實際需要對形狀和顏色進行組合
對于有兩個變化維度(即兩個變化的原因)的系統,采用方案二來進行設計系統中類的個數更少,且系統擴展更為方便。設計方案二即是橋接模式的應用。橋接模式將繼承關系轉換為關聯關系,從而降低了類與類之間的耦合,減少了代碼編寫量。
二、模式定義
橋接模式(Bridge Pattern):將抽象部分與它的實現部分分離,使它們都可以獨立地變化。它是一種對象結構型模式,又稱為柄體(Handle and Body)模式或接口(Interface)模式。
三、模式結構
橋接模式包含如下角色:
- Abstraction:抽象類,抽象部分的接口。通常在這個對象里面,要維護一個實現部分的對象引用,在抽象對象里面的方法,需要調用實現部分的對象來完成。這個對象里面的方法,通常都是跟具體的業務相關的方法。
- RefinedAbstraction:擴充抽象類,擴展抽象部分的接口,通常在這些對象里面,定義跟實際業務相關的方法,這些方法的實現通常會使用Abstraction中定義的方法,也可能需要調用實現部分的對象來完成。
- Implementor:實現類接口,定義實現部分的接口,這個接口不用和Abstraction里面的方法一致,通常是由Implementor接口提供基本的操作,而Abstraction里面定義的是基于這些基本操作的業務方法,也就是說Abstraction定義了基于這些基本操作的較高層次的操作。
- ConcreteImplementor:具體實現類,真正實現Implementor接口的對象。
bridge.jpg
四、代碼分析
其實就是一個排列組合的過程,將需要發送的消息類型和發送的類型進行組合,如果每個組合形式都要定義一個類的話,會顯得很冗余,通過橋接模式就可以很簡潔。
定義發送方式:
public interface MessageImplementor {
public void send(String message,String toUser);
}
public class MessageMobile implements MessageImplementor {
@Override
public void send(String message, String toUser) {
System.out.println("使用手機短消息的方式,發送消息'" + message + "'給" + toUser);
}
}
public class MessageSMS implements MessageImplementor {
@Override
public void send(String message, String toUser) {
System.out.println("使用站內短消息的方式,發送消息'" + message + "'給" + toUser);
}
}
public class MessageEmail implements MessageImplementor {
@Override
public void send(String message, String toUser) {
System.out.println("使用Email的方式,發送消息'" + message + "'給" + toUser);
}
}
定義消息類:
public abstract class AbstractMessage {
protected MessageImplementor impl;
public AbstractMessage(MessageImplementor impl) {
this.impl = impl;
}
public void sendMessage(String message, String toUser) {
this.impl.send(message, toUser);
}
}
public class CommonMessage extends AbstractMessage {
public CommonMessage(MessageImplementor impl) {
super(impl);
}
public void sendMessage(String message, String toUser) {
//對于普通消息,什么都不干,直接調父類的方法,把消息發送出去就可以了
super.sendMessage(message, toUser);
}
}
public class SpecialUrgencyMessage extends AbstractMessage {
public SpecialUrgencyMessage(MessageImplementor impl) {
super(impl);
}
public void hurry(String messageId) {
//執行催促的業務,發出催促的信息
}
public void sendMessage(String message, String toUser) {
message = "特急:" + message;
super.sendMessage(message, toUser);
//還需要增加一條待催促的信息
}
}
public class UrgencyMessage extends AbstractMessage {
public UrgencyMessage(MessageImplementor impl) {
super(impl);
}
public void sendMessage(String message, String toUser) {
message = "加急:" + message;
super.sendMessage(message, toUser);
}
public Object watch(String messageId) {
//獲取相應的數據,組織成監控的數據對象,然后返回
return null;
}
}
測試代碼:
public class Client {
public static void main(String[] args) {
//創建具體的實現對象
MessageImplementor impl = new MessageSMS();
//創建一個普通消息對象
AbstractMessage m = new CommonMessage(impl);
m.sendMessage("請喝一杯茶", "小李");
//創建一個緊急消息對象
m = new UrgencyMessage(impl);
m.sendMessage("請喝一杯茶", "小李");
//創建一個特急消息對象
m = new SpecialUrgencyMessage(impl);
m.sendMessage("請喝一杯茶", "小李");
//把實現方式切換成手機短消息,然后再實現一遍
impl = new MessageMobile();
m = new CommonMessage(impl);
m.sendMessage("請喝一杯茶", "小李");
m = new UrgencyMessage(impl);
m.sendMessage("請喝一杯茶", "小李");
m = new SpecialUrgencyMessage(impl);
m.sendMessage("請喝一杯茶", "小李");
}
}
返回結果:
使用站內短消息的方式,發送消息'請喝一杯茶'給小李
使用站內短消息的方式,發送消息'加急:請喝一杯茶'給小李
使用站內短消息的方式,發送消息'特急:請喝一杯茶'給小李
使用手機短消息的方式,發送消息'請喝一杯茶'給小李
使用手機短消息的方式,發送消息'加急:請喝一杯茶'給小李
使用手機短消息的方式,發送消息'特急:請喝一杯茶'給小李
五、模式分析
理解橋接模式,重點需要理解如何將抽象化(Abstraction)與實現化(Implementation)脫耦,使得二者可以獨立地變化。
- 抽象化:抽象化就是忽略一些信息,把不同的實體當作同樣的實體對待。在面向對象中,將對象的共同性質抽取出來形成類的過程即為抽象化的過程。
- 實現化:針對抽象化給出的具體實現,就是實現化,抽象化與實現化是一對互逆的概念,實現化產生的對象比抽象化更具體,是對抽象化事物的進一步具體化的產物。
- 脫耦:脫耦就是將抽象化和實現化之間的耦合解脫開,或者說是將它們之間的強關聯改換成弱關聯,將兩個角色之間的繼承關系改為關聯關系。橋接模式中的所謂脫耦,就是指在一個軟件系統的抽象化和實現化之間使用關聯關系(組合或者聚合關系)而不是繼承關系,從而使兩者可以相對獨立地變化,這就是橋接模式的用意。
六、優點
橋接模式的優點:
- 分離抽象接口及其實現部分。
- 橋接模式有時類似于多繼承方案,但是多繼承方案違背了類的單一職責原則(即一個類只有一個變化的原因),復用性比較差,而且多繼承結構中類的個數非常龐大,橋接模式是比多繼承方案更好的解決方法。
- 橋接模式提高了系統的可擴充性,在兩個變化維度中任意擴展一個維度,都不需要修改原有系統。
- 實現細節對客戶透明,可以對用戶隱藏實現細節。
七、缺點
橋接模式的缺點:
- 橋接模式的引入會增加系統的理解與設計難度,由于聚合關聯關系建立在抽象層,要求開發者針對抽象進行設計與編程。
- 橋接模式要求正確識別出系統中兩個獨立變化的維度,因此其使用范圍具有一定的局限性。
八、適用環境
在以下情況下可以使用橋接模式:
- 如果一個系統需要在構件的抽象化角色和具體化角色之間增加更多的靈活性,避免在兩個層次之間建立靜態的繼承聯系,通過橋接模式可以使它們在抽象層建立一個關聯關系。
- 抽象化角色和實現化角色可以以繼承的方式獨立擴展而互不影響,在程序運行時可以動態將一個抽象化子類的對象和一個實現化子類的對象進行組合,即系統需要對抽象化角色和實現化角色進行動態耦合。
- 一個類存在兩個獨立變化的維度,且這兩個維度都需要進行擴展。
- 雖然在系統中使用繼承是沒有問題的,但是由于抽象化角色和具體化角色需要獨立變化,設計要求需要獨立管理這兩者。
- 對于那些不希望使用繼承或因為多層次繼承導致系統類的個數急劇增加的系統,橋接模式尤為適用。
九、模式擴展
適配器模式與橋接模式的聯用:
- 橋接模式和適配器模式用于設計的不同階段,橋接模式用于系統的初步設計,對于存在兩個獨立變化維度的類可以將其分為抽象化和實現化兩個角色,使它們可以分別進行變化;而在初步設計完成之后,當發現系統與已有類無法協同工作時,可以采用適配器模式。但有時候在設計初期也需要考慮適配器模式,特別是那些涉及到大量第三方應用接口的情況。
十、總結
- 橋接模式將抽象部分與它的實現部分分離,使它們都可以獨立地變化。它是一種對象結構型模式,又稱為柄體(Handle and Body)模式或接口(Interface)模式。
- 橋接模式包含如下四個角色:抽象類中定義了一個實現類接口類型的對象并可以維護該對象;擴充抽象類擴充由抽象類定義的接口,它實現了在抽象類中定義的抽象業務方法,在擴充抽象類中可以調用在實現類接口中定義的業務方法;實現類接口定義了實現類的接口,實現類接口僅提供基本操作,而抽象類定義的接口可能會做更多更復雜的操作;具體實現類實現了實現類接口并且具體實現它,在不同的具體實現類中提供基本操作的不同實現,在程序運行時,具體實現類對象將替換其父類對象,提供給客戶端具體的業務操作方法。
- 在橋接模式中,抽象化(Abstraction)與實現化(Implementation)脫耦,它們可以沿著各自的維度獨立變化。
- 橋接模式的主要優點是分離抽象接口及其實現部分,是比多繼承方案更好的解決方法,橋接模式還提高了系統的可擴充性,在兩個變化維度中任意擴展一個維度,都不需要修改原有系統,實現細節對客戶透明,可以對用戶隱藏實現細節;其主要缺點是增加系統的理解與設計難度,且識別出系統中兩個獨立變化的維度并不是一件容易的事情。
- 橋接模式適用情況包括:需要在構件的抽象化角色和具體化角色之間增加更多的靈活性,避免在兩個層次之間建立靜態的繼承聯系;抽象化角色和實現化角色可以以繼承的方式獨立擴展而互不影響;一個類存在兩個獨立變化的維度,且這兩個維度都需要進行擴展;設計要求需要獨立管理抽象化角色和具體化角色;不希望使用繼承或因為多層次繼承導致系統類的個數急劇增加的系統。