1 場景問題#
1.1 發(fā)送提示消息##
考慮這樣一個實際的業(yè)務功能:發(fā)送提示消息?;旧纤袔I(yè)務流程處理的系統(tǒng)都會有這樣的功能,比如某人有新的工作了,需要發(fā)送一條消息提示他。
從業(yè)務上看,消息又分成普通消息、加急消息和特急消息多種,不同的消息類型,業(yè)務功能處理是不一樣的,比如加急消息是在消息上添加加急,而特急消息除了添加特急外,還會做一條催促的記錄,多久不完成會繼續(xù)催促。從發(fā)送消息的手段上看,又有系統(tǒng)內(nèi)短消息、手機短消息、郵件等等。
現(xiàn)在要實現(xiàn)這樣的發(fā)送提示消息的功能,該如何實現(xiàn)呢?
1.2 不用模式的解決方案##
- 實現(xiàn)簡化版本
先考慮實現(xiàn)一個簡單點的版本,比如:消息先只是實現(xiàn)發(fā)送普通消息,發(fā)送的方式呢,先實現(xiàn)系統(tǒng)內(nèi)短消息和郵件。其它的功能,等這個版本完成過后,再繼續(xù)添加,這樣先把問題簡單化,實現(xiàn)起來會容易一點。
(1)由于發(fā)送普通消息會有兩種不同的實現(xiàn)方式,為了讓外部能統(tǒng)一操作,因此,把消息設計成接口,然后由兩個不同的實現(xiàn)類,分別實現(xiàn)系統(tǒng)內(nèi)短消息方式和郵件發(fā)送消息的方式。此時系統(tǒng)結構如圖所示:
(2)先來看看消息的統(tǒng)一接口,示例代碼如下:
/**
* 消息的統(tǒng)一接口
*/
public interface Message {
/**
* 發(fā)送消息
* @param message 要發(fā)送的消息內(nèi)容
* @param toUser 消息發(fā)送的目的人員
*/
public void send(String message,String toUser);
}
(3)再來分別看看兩種實現(xiàn)方式,這里只是為了示意,并不會真的去發(fā)送Email和站內(nèi)短消息,先看站內(nèi)短消息的方式,示例代碼如下:
/**
* 以站內(nèi)短消息的方式發(fā)送普通消息
*/
public class CommonMessageSMS implements Message {
public void send(String message, String toUser) {
System.out.println("使用站內(nèi)短消息的方式,發(fā)送消息'"+message+"'給"+toUser);
}
}
/**
* 以Email的方式發(fā)送普通消息
*/
public class CommonMessageEmail implements Message {
public void send(String message, String toUser) {
System.out.println("使用Email的方式,發(fā)送消息'"+message+"'給"+toUser);
}
}
- 實現(xiàn)發(fā)送加急消息
上面的實現(xiàn),看起來很簡單,對不對。接下來,添加發(fā)送加急消息的功能,也有兩種發(fā)送的方式,同樣是站內(nèi)短消息和Email的方式。
加急消息的實現(xiàn)跟普通消息不同,加急消息會自動在消息上添加加急,然后再發(fā)送消息;另外加急消息會提供監(jiān)控的方法,讓客戶端可以隨時通過這個方法來了解對于加急消息處理的進度,比如:相應的人員是否接收到這個信息,相應的工作是否已經(jīng)開展等等。因此加急消息需要擴展出一個新的接口,除了基本的發(fā)送消息的功能,還需要添加監(jiān)控的功能,這個時候,系統(tǒng)的結構如圖所示:
(1)先看看擴展出來的加急消息的接口,示例代碼如下:
/**
* 加急消息的抽象接口
*/
public interface UrgencyMessage extends Message{
/**
* 監(jiān)控某消息的處理過程
* @param messageId 被監(jiān)控的消息的編號
* @return 包含監(jiān)控到的數(shù)據(jù)對象,這里示意一下,所以用了Object
*/
public Object watch(String messageId);
}
(2)相應的實現(xiàn)方式還是發(fā)送站內(nèi)短消息和Email兩種,同樣需要兩個實現(xiàn)類來分別實現(xiàn)這兩種方式,先看站內(nèi)短消息的方式,示例代碼如下:
public class UrgencyMessageSMS implements UrgencyMessage {
public void send(String message, String toUser) {
message = "加急:"+message;
System.out.println("使用站內(nèi)短消息的方式,發(fā)送消息'"+message+"'給"+toUser);
}
public Object watch(String messageId) {
//獲取相應的數(shù)據(jù),組織成監(jiān)控的數(shù)據(jù)對象,然后返回
return null;
}
}
public class UrgencyMessageEmail implements UrgencyMessage {
public void send(String message, String toUser) {
message = "加急:"+message;
System.out.println("使用Email的方式,發(fā)送消息'"+message+"'給"+toUser);
}
public Object watch(String messageId) {
//獲取相應的數(shù)據(jù),組織成監(jiān)控的數(shù)據(jù)對象,然后返回
return null;
}
}
(3)事實上,在實現(xiàn)加急消息發(fā)送的功能上,可能會使用前面發(fā)送不同消息的功能,也就是讓實現(xiàn)加急消息處理的對象繼承普通消息的相應實現(xiàn),這里為了讓結構簡單一點,清晰一點,所以沒有這樣做。
1.3 有何問題##
上面這樣實現(xiàn),好像也能滿足基本的功能要求,可是這么實現(xiàn)好不好呢?有沒有什么問題呢?
咱們繼續(xù)向下來添加功能實現(xiàn),為了簡潔,就不再去進行代碼示意了,通過實現(xiàn)的結構示意圖就可以看出實現(xiàn)上的問題。
- 繼續(xù)添加特急消息的處理
特急消息不需要查看處理進程,只要沒有完成,就直接催促,也就是說,對于特急消息,在普通消息的處理基礎上,需要添加催促的功能。而特急消息、還有催促的發(fā)送方式,相應的實現(xiàn)方式還是發(fā)送站內(nèi)短消息和Email兩種,此時系統(tǒng)的結構如圖3所示:
仔細觀察上面的系統(tǒng)結構示意圖,會發(fā)現(xiàn)一個很明顯的問題,那就是:通過這種繼承的方式來擴展消息處理,會非常不方便。
你看,實現(xiàn)加急消息處理的時候,必須實現(xiàn)站內(nèi)短消息和Email兩種處理方式,因為業(yè)務處理可能不同;在實現(xiàn)特急消息處理的時候,又必須實現(xiàn)站內(nèi)短消息和Email這兩種處理方式。
這意味著,以后每次擴展一下消息處理,都必須要實現(xiàn)這兩種處理方式,是不是很痛苦,這還不算完,如果要添加新的實現(xiàn)方式呢?繼續(xù)向下看吧。
- 繼續(xù)添加發(fā)送手機消息的處理方式
如果看到上面的實現(xiàn),你還感覺問題不是很大的話,繼續(xù)完成功能,添加發(fā)送手機消息的處理方式。
仔細觀察現(xiàn)在的實現(xiàn),如果要添加一種新的發(fā)送消息的方式,是需要在每一種抽象的具體實現(xiàn)里面,都要添加發(fā)送手機消息的處理的。也就是說:發(fā)送普通消息、加急消息和特急消息的處理,都可以通過手機來發(fā)送。這就意味著,需要添加三個實現(xiàn)。此時系統(tǒng)結構如圖所示:
- 小結一下出現(xiàn)的問題
采用通過繼承來擴展的實現(xiàn)方式,有個明顯的缺點:擴展消息的種類不太容易,不同種類的消息具有不同的業(yè)務,也就是有不同的實現(xiàn),在這種情況下,每個種類的消息,需要實現(xiàn)所有不同的消息發(fā)送方式。
更可怕的是,如果要新加入一種消息的發(fā)送方式,那么會要求所有的消息種類,都要加入這種新的發(fā)送方式的實現(xiàn)。
要是考慮業(yè)務功能上再擴展一下呢?比如:要求實現(xiàn)群發(fā)消息,也就是一次可以發(fā)送多條消息,這就意味著很多地方都得修改,太恐怖了。
那么究竟該如何實現(xiàn)才能既實現(xiàn)功能,又能靈活的擴展呢?
2 解決方案#
2.1 橋接模式來解決##
用來解決上述問題的一個合理的解決方案,就是使用橋接模式。那么什么是橋接模式呢?
- 橋接模式定義
將抽象部分與它的實現(xiàn)部分分離,使它們都可以獨立地變化。
- 應用橋接模式來解決的思路
仔細分析上面的示例,根據(jù)示例的功能要求,示例的變化具有兩個緯度,一個緯度是抽象的消息這邊,包括普通消息、加急消息和特急消息,這幾個抽象的消息本身就具有一定的關系,加急消息和特急消息會擴展普通消息;另一個緯度在具體的消息發(fā)送方式上,包括站內(nèi)短消息、Email和手機短信息,這幾個方式是平等的,可被切換的方式。這兩個緯度一共可以組合出9種不同的可能性來,它們的關系如下圖所示:
現(xiàn)在出現(xiàn)問題的根本原因,就在于消息的抽象和實現(xiàn)是混雜在一起的,這就導致了,一個緯度的變化,會引起另一個緯度進行相應的變化,從而使得程序擴展起來非常困難。
要想解決這個問題,就必須把這兩個緯度分開,也就是將抽象部分和實現(xiàn)部分分開,讓它們相互獨立,這樣就可以實現(xiàn)獨立的變化,使擴展變得簡單。
橋接模式通過引入實現(xiàn)的接口,把實現(xiàn)部分從系統(tǒng)中分離出去;那么,抽象這邊如何使用具體的實現(xiàn)呢?肯定是面向實現(xiàn)的接口來編程了,為了讓抽象這邊能夠很方便的與實現(xiàn)結合起來,把頂層的抽象接口改成抽象類,在里面持有一個具體的實現(xiàn)部分的實例。
這樣一來,對于需要發(fā)送消息的客戶端而言,就只需要創(chuàng)建相應的消息對象,然后調(diào)用這個消息對象的方法就可以了,這個消息對象會調(diào)用持有的真正的消息發(fā)送方式來把消息發(fā)送出去。也就是說客戶端只是想要發(fā)送消息而已,并不想關心具體如何發(fā)送。
2.2 模式結構和說明##
Abstraction:抽象部分的接口。通常在這個對象里面,要維護一個實現(xiàn)部分的對象引用,在抽象對象里面的方法,需要調(diào)用實現(xiàn)部分的對象來完成。這個對象里面的方法,通常都是跟具體的業(yè)務相關的方法。
RefinedAbstraction:擴展抽象部分的接口,通常在這些對象里面,定義跟實際業(yè)務相關的方法,這些方法的實現(xiàn)通常會使用Abstraction中定義的方法,也可能需要調(diào)用實現(xiàn)部分的對象來完成。
Implementor:定義實現(xiàn)部分的接口,這個接口不用和Abstraction里面的方法一致,通常是由Implementor接口提供基本的操作,而Abstraction里面定義的是基于這些基本操作的業(yè)務方法,也就是說Abstraction定義了基于這些基本操作的較高層次的操作。
ConcreteImplementor:真正實現(xiàn)Implementor接口的對象。
2.3 橋接模式示例代碼##
- 先看看Implementor接口的定義,示例代碼如下:
/**
* 定義實現(xiàn)部分的接口,可以與抽象部分接口的方法不一樣
*/
public interface Implementor {
/**
* 示例方法,實現(xiàn)抽象部分需要的某些具體功能
*/
public void operationImpl();
}
- 再看看Abstraction接口的定義,注意一點,雖然說是接口定義,但其實是實現(xiàn)成為抽象類。示例代碼如下:
/**
* 定義抽象部分的接口
*/
public abstract class Abstraction {
/**
* 持有一個實現(xiàn)部分的對象
*/
protected Implementor impl;
/**
* 構造方法,傳入實現(xiàn)部分的對象
* @param impl 實現(xiàn)部分的對象
*/
public Abstraction(Implementor impl){
this.impl = impl;
}
/**
* 示例操作,實現(xiàn)一定的功能,可能需要轉調(diào)實現(xiàn)部分的具體實現(xiàn)方法
*/
public void operation() {
impl.operationImpl();
}
}
- 該來看看具體的實現(xiàn)了,示例代碼如下:
/**
* 真正的具體實現(xiàn)對象
*/
public class ConcreteImplementorA implements Implementor {
public void operationImpl() {
//真正的實現(xiàn)
}
}
/**
* 真正的具體實現(xiàn)對象
*/
public class ConcreteImplementorB implements Implementor {
public void operationImpl() {
//真正的實現(xiàn)
}
}
- 最后來看看擴展Abstraction接口的對象實現(xiàn),示例代碼如下:
/**
* 擴充由Abstraction定義的接口功能
*/
public class RefinedAbstraction extends Abstraction {
public RefinedAbstraction(Implementor impl) {
super(impl);
}
/**
* 示例操作,實現(xiàn)一定的功能
*/
public void otherOperation(){
//實現(xiàn)一定的功能,可能會使用具體實現(xiàn)部分的實現(xiàn)方法,
//但是本方法更大的可能是使用Abstraction中定義的方法,
//通過組合使用Abstraction中定義的方法來完成更多的功能
}
}
2.4 使用橋接模式重寫示例##
學習了橋接模式的基礎知識過后,該來使用橋接模式重寫前面的示例了。通過示例,來看看使用橋接模式來實現(xiàn)同樣的功能,是否能解決“既能方便的實現(xiàn)功能,又能有很好的擴展性”的問題。
要使用橋接模式來重新實現(xiàn)前面的示例,首要任務就是要把抽象部分和實現(xiàn)部分分離出來,分析要實現(xiàn)的功能,抽象部分就是各個消息的類型所對應的功能,而實現(xiàn)部分就是各種發(fā)送消息的方式。
其次要按照橋接模式的結構,給抽象部分和實現(xiàn)部分分別定義接口,然后分別實現(xiàn)它們就可以了。
- 從簡單功能開始
從相對簡單的功能開始,先實現(xiàn)普通消息和加急消息的功能,發(fā)送方式先實現(xiàn)站內(nèi)短消息和Email這兩種。
使用橋接模式來實現(xiàn)這些功能的程序結構如圖所示:
(1)先看看實現(xiàn)部分定義的接口,示例代碼如下:
/**
* 實現(xiàn)發(fā)送消息的統(tǒng)一接口
*/
public interface MessageImplementor {
/**
* 發(fā)送消息
* @param message 要發(fā)送的消息內(nèi)容
* @param toUser 消息發(fā)送的目的人員
*/
public void send(String message,String toUser);
}
(2)再看看抽象部分定義的接口,示例代碼如下:
/**
* 抽象的消息對象
*/
public abstract class AbstractMessage {
/**
* 持有一個實現(xiàn)部分的對象
*/
protected MessageImplementor impl;
/**
* 構造方法,傳入實現(xiàn)部分的對象
* @param impl 實現(xiàn)部分的對象
*/
public AbstractMessage(MessageImplementor impl){
this.impl = impl;
}
/**
* 發(fā)送消息,轉調(diào)實現(xiàn)部分的方法
* @param message 要發(fā)送的消息內(nèi)容
* @param toUser 消息發(fā)送的目的人員
*/
public void sendMessage(String message,String toUser){
this.impl.send(message, toUser);
}
}
(3)看看如何具體的實現(xiàn)發(fā)送消息,先看站內(nèi)短消息的實現(xiàn)吧,示例代碼如下:
/**
* 以站內(nèi)短消息的方式發(fā)送消息
*/
public class MessageSMS implements MessageImplementor{
public void send(String message, String toUser) {
System.out.println("使用站內(nèi)短消息的方式,發(fā)送消息'"+message+"'給"+toUser);
}
}
/**
* 以Email的方式發(fā)送消息
*/
public class MessageEmail implements MessageImplementor{
public void send(String message, String toUser) {
System.out.println("使用Email的方式,發(fā)送消息'"+message+"'給"+toUser);
}
}
(4)接下來該看看如何擴展抽象的消息接口了,先看普通消息的實現(xiàn),示例代碼如下:
public class CommonMessage extends AbstractMessage{
public CommonMessage(MessageImplementor impl) {
super(impl);
}
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);
}
public void sendMessage(String message, String toUser) {
message = "加急:"+message;
super.sendMessage(message, toUser);
}
/**
* 擴展自己的新功能:監(jiān)控某消息的處理過程
* @param messageId 被監(jiān)控的消息的編號
* @return 包含監(jiān)控到的數(shù)據(jù)對象,這里示意一下,所以用了Object
*/
public Object watch(String messageId) {
//獲取相應的數(shù)據(jù),組織成監(jiān)控的數(shù)據(jù)對象,然后返回
return null;
}
}
- 添加功能
看了上面的實現(xiàn),發(fā)現(xiàn)使用橋接模式來實現(xiàn)也不是很困難啊,關鍵得看是否能解決前面提出的問題,那就來添加還未實現(xiàn)的功能看看,添加對特急消息的處理,同時添加一個使用手機發(fā)送消息的方式。該怎么實現(xiàn)呢?
很簡單,只需要在抽象部分再添加一個特急消息的類,擴展抽象消息就可以把特急消息的處理功能加入到系統(tǒng)中了;對于添加手機發(fā)送消息的方式也很簡單,在實現(xiàn)部分新增加一個實現(xiàn)類,實現(xiàn)用手機發(fā)送消息的方式,也就可以了。
這么簡單?好像看起來完全沒有了前面所提到的問題。的確如此,采用橋接模式來實現(xiàn)過后,抽象部分和實現(xiàn)部分分離開了,可以相互獨立的變化,而不會相互影響。因此在抽象部分添加新的消息處理,對發(fā)送消息的實現(xiàn)部分是沒有影響的;反過來增加發(fā)送消息的方式,對消息處理部分也是沒有影響的。
(1)接著看看代碼實現(xiàn),先看看新的特急消息的處理類,示例代碼如下:
public class SpecialUrgencyMessage extends AbstractMessage{
public SpecialUrgencyMessage(MessageImplementor impl) {
super(impl);
}
public void hurry(String messageId) {
//執(zhí)行催促的業(yè)務,發(fā)出催促的信息
}
public void sendMessage(String message, String toUser) {
message = "特急:"+message;
super.sendMessage(message, toUser);
//還需要增加一條待催促的信息
}
}
(2)再看看使用手機短消息的方式發(fā)送消息的實現(xiàn),示例代碼如下:
/**
* 以手機短消息的方式發(fā)送消息
*/
public class MessageMobile implements MessageImplementor{
public void send(String message, String toUser) {
System.out.println("使用手機短消息的方式,發(fā)送消息'"+message+"'給"+toUser);
}
}
- 測試一下功能
看了上面的實現(xiàn),可能會感覺得到,使用橋接模式來實現(xiàn)前面的示例過后,添加新的消息處理,或者是新的消息發(fā)送方式是如此簡單,可是這樣實現(xiàn),好用嗎?寫個客戶端來測試和體會一下,示例代碼如下:
public class Client {
public static void main(String[] args) {
//創(chuàng)建具體的實現(xiàn)對象
MessageImplementor impl = new MessageSMS();
//創(chuàng)建一個普通消息對象
AbstractMessage m = new CommonMessage(impl);
m.sendMessage("請喝一杯茶", "小李");
//創(chuàng)建一個緊急消息對象
m = new UrgencyMessage(impl);
m.sendMessage("請喝一杯茶", "小李");
//創(chuàng)建一個特急消息對象
m = new SpecialUrgencyMessage(impl);
m.sendMessage("請喝一杯茶", "小李");
//把實現(xiàn)方式切換成手機短消息,然后再實現(xiàn)一遍
impl = new MessageMobile();
m = new CommonMessage(impl);
m.sendMessage("請喝一杯茶", "小李");
m = new UrgencyMessage(impl);
m.sendMessage("請喝一杯茶", "小李");
m = new SpecialUrgencyMessage(impl);
m.sendMessage("請喝一杯茶", "小李");
}
}
運行結果如下:
使用站內(nèi)短消息的方式,發(fā)送消息'請喝一杯茶'給小李
使用站內(nèi)短消息的方式,發(fā)送消息'加急:請喝一杯茶'給小李
使用站內(nèi)短消息的方式,發(fā)送消息'特急:請喝一杯茶'給小李
使用手機短消息的方式,發(fā)送消息'請喝一杯茶'給小李
使用手機短消息的方式,發(fā)送消息'加急:請喝一杯茶'給小李
使用手機短消息的方式,發(fā)送消息'特急:請喝一杯茶'給小李
3 模式講解#
3.1 認識橋接模式##
- 什么是橋接
在橋接模式里面,不太好理解的就是橋接的概念,什么是橋接?為何需要橋接?如何橋接?把這些問題搞清楚了,也就基本明白橋接的含義了。
一個一個來,先看什么是橋接?所謂橋接,通俗點說就是在不同的東西之間搭一個橋,讓他們能夠連接起來,可以相互通訊和使用。那么在橋接模式中到底是給什么東西來搭橋呢?就是為被分離了的抽象部分和實現(xiàn)部分來搭橋,比如前面示例中抽象的消息和具體消息發(fā)送之間搭個橋。
但是這里要注意一個問題:在橋接模式中的橋接是單向的,也就是只能是抽象部分的對象去使用具體實現(xiàn)部分的對象,而不能反過來,也就是個單向橋。
- 為何需要橋接
為了達到讓抽象部分和實現(xiàn)部分都可以獨立變化的目的,在橋接模式中,是把抽象部分和實現(xiàn)部分分離開來的,雖然從程序結構上是分開了,但是在抽象部分實現(xiàn)的時候,還是需要使用具體的實現(xiàn)的,這可怎么辦呢?抽象部分如何才能調(diào)用到具體實現(xiàn)部分的功能呢?很簡單,搭個橋不就可以了,搭個橋,讓抽象部分通過這個橋就可以調(diào)用到實現(xiàn)部分的功能了,因此需要橋接。
- 如何橋接
這個理解上也很簡單,只要讓抽象部分擁有實現(xiàn)部分的接口對象,這就橋接上了,在抽象部分就可以通過這個接口來調(diào)用具體實現(xiàn)部分的功能。也就是說,橋接在程序上就體現(xiàn)成了在抽象部分擁有實現(xiàn)部分的接口對象,維護橋接就是維護這個關系。
- 獨立變化
橋接模式的意圖:使得抽象和實現(xiàn)可以獨立變化,都可以分別擴充。也就是說抽象部分和實現(xiàn)部分是一種非常松散的關系,從某個角度來講,抽象部分和實現(xiàn)部分是可以完全分開的,獨立的,抽象部分不過是一個使用實現(xiàn)部分對外接口的程序罷了。
如果這么看橋接模式的話,就類似于策略模式了,抽象部分需要根據(jù)某個策略,來選擇真實的實現(xiàn),也就是說橋接模式的抽象部分相當于策略模式的上下文。更原始的就直接類似于面向接口編程,通過接口分離的兩個部分而已。但是別忘了,橋接模式的抽象部分,是可以繼續(xù)擴展和變化的,而策略模式只有上下文,是不存在所謂抽象部分的。
那抽象和實現(xiàn)為何還要組合在一起呢?原因是在抽象部分和實現(xiàn)部分還是存在內(nèi)部聯(lián)系的,抽象部分的實現(xiàn)通常是需要調(diào)用實現(xiàn)部分的功能來實現(xiàn)的。
- 動態(tài)變換功能
由于橋接模式中的抽象部分和實現(xiàn)部分是完全分離的,因此可以在運行時動態(tài)組合具體的真實實現(xiàn),從而達到動態(tài)變換功能的目的。
從另外一個角度看,抽象部分和實現(xiàn)部分沒有固定的綁定關系了,因此同一個真實實現(xiàn)可以被不同的抽象對象使用,反過來,同一個抽象也可以有多個不同的實現(xiàn)。就像前面示例的那樣,比如:站內(nèi)短消息的實現(xiàn)功能,可以被普通消息、加急消息或是特急消息等不同的消息對象使用;反過來,某個消息具體的發(fā)送方式,可以是站內(nèi)短消息,或者是Email,也可以是手機短消息等具體的發(fā)送方式。
- 退化的橋接模式
如果Implementor僅有一個實現(xiàn),那么就沒有必要創(chuàng)建Implementor接口了,這是一種橋接模式退化的情況。這個時候Abstraction和Implementor是一對一的關系,雖然如此,也還是要保持它們的分離狀態(tài),這樣的話,它們才不會相互影響,才可以分別擴展。
也就是說,就算不要Implementor接口了,也要保持Abstraction和Implementor是分離的,模式的分離機制仍然是非常有用的。
- 橋接模式和繼承
繼承是擴展對象功能的一種常見手段,通常情況下,繼承擴展的功能變化緯度都是一緯的,也就是變化的因素只有一類。
對于出現(xiàn)變化因素有兩類的,也就是有兩個變化緯度的情況,繼承實現(xiàn)就會比較痛苦。比如上面的示例,就有兩個變化緯度,一個是消息的類別,不同的消息類別處理不同;另外一個是消息的發(fā)送方式。
從理論上來說,如果用繼承的方式來實現(xiàn)這種有兩個變化緯度的情況,最后實際的實現(xiàn)類應該是兩個緯度上可變數(shù)量的乘積那么多個。比如上面的示例,在消息類別的緯度上,目前的可變數(shù)量是3個,普通消息、加急消息和特急消息;在消息發(fā)送方式的緯度上,目前的可變數(shù)量也是3個,站內(nèi)短消息、Email和手機短消息。這種情況下,如果要實現(xiàn)全的話,那么需要的實現(xiàn)類應該是:3 X 3 = 9個。
如果要在任何一個緯度上進行擴展,都需要實現(xiàn)另外一個緯度上的可變數(shù)量那么多個實現(xiàn)類,這也是為何會感到擴展起來很困難。而且隨著程序規(guī)模的加大,會越來越難以擴展和維護。
而橋接模式就是用來解決這種有兩個變化緯度的情況下,如何靈活的擴展功能的一個很好的方案。其實,橋接模式主要是把繼承改成了使用對象組合,從而把兩個緯度分開,讓每一個緯度單獨去變化,最后通過對象組合的方式,把兩個緯度組合起來,每一種組合的方式就相當于原來繼承中的一種實現(xiàn),這樣就有效的減少了實際實現(xiàn)的類的個數(shù)。
從理論上來說,如果用橋接模式的方式來實現(xiàn)這種有兩個變化緯度的情況,最后實際的實現(xiàn)類應該是兩個緯度上可變數(shù)量的和那么多個。同樣是上面那個示例,使用橋接模式來實現(xiàn),實現(xiàn)全的話,最后需要的實現(xiàn)類的數(shù)目應該是:3 + 3 = 6個。
這也從側面體現(xiàn)了,使用對象組合的方式比繼承要來得更靈活。
- 橋接模式的調(diào)用順序示意圖
3.2 誰來橋接##
所謂誰來橋接,就是誰來負責創(chuàng)建抽象部分和實現(xiàn)部分的關系,說得更直白點,就是誰來負責創(chuàng)建Implementor的對象,并把它設置到抽象部分的對象里面去,這點對于使用橋接模式來說,是十分重要的一點。
大致有如下幾種實現(xiàn)方式:
由客戶端負責創(chuàng)建Implementor的對象,并在創(chuàng)建抽象部分的對象的時候,把它設置到抽象部分的對象里面去,前面的示例采用的就是這個方式
可以在抽象部分的對象構建的時候,由抽象部分的對象自己來創(chuàng)建相應的Implementor的對象,當然可以給它傳遞一些參數(shù),它可以根據(jù)參數(shù)來選擇并創(chuàng)建具體的Implementor的對象
可以在Abstraction中選擇并創(chuàng)建一個缺省的Implementor的對象,然后子類可以根據(jù)需要改變這個實現(xiàn)
也可以使用抽象工廠或者簡單工廠來選擇并創(chuàng)建具體的Implementor的對象,抽象部分的類可以通過調(diào)用工廠的方法來獲取Implementor的對象
如果使用IoC/DI容器的話,還可以通過IoC/DI容器來創(chuàng)建具體的Implementor的對象,并注入回到Abstraction中
- 由抽象部分的對象自己來創(chuàng)建相應的Implementor的對象
對于這種情況的實現(xiàn),又分成兩種,一種是需要外部傳入?yún)?shù),一種是不需要外部傳入?yún)?shù)。
(1)從外面?zhèn)鬟f參數(shù)比較簡單,比如前面的示例,如果用一個type來標識具體采用哪種發(fā)送消息的方案,然后在Abstraction的構造方法中,根據(jù)type進行創(chuàng)建就好了。
/**
* 抽象的消息對象
*/
public abstract class AbstractMessage {
/**
* 持有一個實現(xiàn)部分的對象
*/
protected MessageImplementor impl;
/**
* 構造方法,傳入選擇實現(xiàn)部分的類型
* @param type 傳入選擇實現(xiàn)部分的類型
*/
public AbstractMessage(int type){
if(type==1){
this.impl = new MessageSMS();
}else if(type==2){
this.impl = new MessageEmail();
}else if(type==3){
this.impl = new MessageMobile();
}
}
/**
* 發(fā)送消息,轉調(diào)實現(xiàn)部分的方法
* @param message 要發(fā)送的消息內(nèi)容
* @param toUser 把消息發(fā)送的目的人員
*/
public void sendMessage(String message,String toUser){
this.impl.send(message, toUser);
}
}
(2)對于不需要外部傳入?yún)?shù)的情況,那就說明是在Abstraction的實現(xiàn)中,根據(jù)具體的參數(shù)數(shù)據(jù)來選擇相應的Implementor對象。有可能在Abstraction的構造方法中選,也有可能在具體的方法中選。
比如前面的示例,如果發(fā)送的消息長度在100以內(nèi)采用手機短消息,長度在100-1000采用站內(nèi)短消息,長度在1000以上采用Email,那么就可以在內(nèi)部方法中自己判斷實現(xiàn)了。
實現(xiàn)中,大致有如下改變:
原來protected的MessageImplementor類型的屬性,不需要了,去掉
提供一個protected的方法來獲取要使用的實現(xiàn)部分的對象,在這個方法里面,根據(jù)消息的長度來選擇合適的實現(xiàn)對象
構造方法什么都不用做了,也不需要傳入?yún)?shù)
在原來使用impl屬性的地方,要修改成通過上面那個方法來獲取合適的實現(xiàn)對象了,不能直接使用impl屬性,否則會沒有值
public abstract class AbstractMessage {
/**
* 構造方法
*/
public AbstractMessage() {
//現(xiàn)在什么都不做了
}
/**
* 發(fā)送消息,轉調(diào)實現(xiàn)部分的方法
* @param message 要發(fā)送的消息內(nèi)容
* @param toUser 把消息發(fā)送的目的人員
*/
public void sendMessage(String message,String toUser) {
this.getImpl(message).send(message, toUser);
}
/**
* 根據(jù)消息的長度來選擇合適的實現(xiàn)
* @param message 要發(fā)送的消息
* @return 合適的實現(xiàn)對象
*/
protected MessageImplementor getImpl(String message) {
MessageImplementor impl = null;
if(message == null) {
//如果沒有消息,默認使用站內(nèi)短消息
impl = new MessageSMS();
} else if(message.length()< 100) {
//如果消息長度在100以內(nèi),使用手機短消息
impl = new MessageMobile();
} else if(message.length()<1000) {
//如果消息長度在100-1000以內(nèi),使用站內(nèi)短消息
impl = new MessageSMS();
} else {
//如果消息長度在1000以上
impl = new MessageEmail();
}
return impl;
}
}
(3)小結一下
對于由抽象部分的對象自己來創(chuàng)建相應的Implementor的對象的這種情況,不管是否需要外部傳入?yún)?shù),優(yōu)點是客戶使用簡單,而且集中控制Implementor對象的創(chuàng)建和切換邏輯;缺點是要求Abstraction知道所有的具體的Implementor實現(xiàn),并知道如何選擇和創(chuàng)建它們,如果今后要擴展Implementor的實現(xiàn),就要求同時修改Abstraction的實現(xiàn),這會很不靈活,使擴展不方便。
- 在Abstraction中創(chuàng)建缺省的Implementor對象
對于這種方式,實現(xiàn)比較簡單,直接在Abstraction的構造方法中,創(chuàng)建一個缺省的Implementor對象,然后子類根據(jù)需要,看是直接使用還是覆蓋掉。示例代碼如下:
public abstract class AbstractMessage {
protected MessageImplementor impl;
/**
* 構造方法
*/
public AbstractMessage(){
//創(chuàng)建一個默認的實現(xiàn)
this.impl = new MessageSMS();
}
public void sendMessage(String message,String toUser){
this.impl.send(message, toUser);
}
}
這種方式其實還可以使用工廠方法,把創(chuàng)建工作延遲到子類。
- 使用抽象工廠或者是簡單工廠
對于這種方式,根據(jù)具體的需要來選擇,如果是想要創(chuàng)建一系列實現(xiàn)對象,那就使用抽象工廠,如果是創(chuàng)建單個的實現(xiàn)對象,那就使用簡單工廠就可以了。
直接在原來創(chuàng)建Implementor對象的地方,直接調(diào)用相應的抽象工廠或者是簡單工廠,來獲取相應的Implementor對象,很簡單,這個就不去示例了。
這種方法的優(yōu)點是Abstraction類不用和任何一個Implementor類直接耦合。
- 使用IoC/DI的方式
對于這種方式,Abstraction的實現(xiàn)就更簡單了,只需要實現(xiàn)注入Implementor對象的方法就可以了,其它的Abstraction就不管了。
IoC/DI容器會負責創(chuàng)建Implementor對象,并設置回到Abstraction對象中,使用IoC/DI的方式,并不會改變Abstraction和Implementor的關系,Abstraction同樣需要持有相應的Implementor對象,同樣會把功能委托給Implementor對象去實現(xiàn)。
3.3 典型例子-JDBC##
在Java應用中,對于橋接模式有一個非常典型的例子,就是:應用程序使用JDBC驅動程序進行開發(fā)的方式。所謂驅動程序,指的是按照預先約定好的接口來操作計算機系統(tǒng)或者是外圍設備的程序。
先簡單的回憶一下使用JDBC進行開發(fā)的過程,簡單的片斷代碼示例如下:
String sql = "具體要操作的sql語句";
// 1:裝載驅動
Class.forName("驅動的名字");
// 2:創(chuàng)建連接
Connection conn = DriverManager.getConnection("連接數(shù)據(jù)庫服務的URL", "用戶名","密碼");
// 3:創(chuàng)建statement或者是preparedStatement
PreparedStatement pstmt = conn.prepareStatement(sql);
// 4:執(zhí)行sql,如果是查詢,再獲取ResultSet
ResultSet rs = pstmt.executeQuery(sql);
// 5:循環(huán)從ResultSet中把值取出來,封裝到數(shù)據(jù)對象中去
while (rs.next()) {
// 取值示意,按名稱取值
String uuid = rs.getString("uuid");
// 取值示意,按索引取值
int age = rs.getInt(2);
}
//6:關閉
rs.close();
pstmt.close();
conn.close();
從上面的示例可以看出,我們寫的應用程序,是面向JDBC的API在開發(fā),這些接口就相當于橋接模式中的抽象部分的接口。那么怎樣得到這些API的呢?是通過DriverManager來得到的。此時的系統(tǒng)結構如圖所示:
那么這些JDBC的API,誰去實現(xiàn)呢?光有接口,沒有實現(xiàn)也不行啊。
該驅動程序登場了,JDBC的驅動程序實現(xiàn)了JDBC的API,驅動程序就相當于橋接模式中的具體實現(xiàn)部分。而且不同的數(shù)據(jù)庫,由于數(shù)據(jù)庫實現(xiàn)不一樣,可執(zhí)行的Sql也不完全一樣,因此對于JDBC驅動的實現(xiàn)也是不一樣的,也就是不同的數(shù)據(jù)庫會有不同的驅動實現(xiàn)。此時驅動程序這邊的程序結構如圖10所示:
有了抽象部分——JDBC的API,有了具體實現(xiàn)部分——驅動程序,那么它們?nèi)绾芜B接起來呢?就是如何橋接呢?
就是前面提到的DriverManager來把它們橋接起來,從某個側面來看,DriverManager在這里起到了類似于簡單工廠的功能,基于JDBC的應用程序需要使用JDBC的API,如何得到呢?就通過DriverManager來獲取相應的對象。
那么此時系統(tǒng)的整體結構如圖所示:
通過上圖可以看出,基于JDBC的應用程序,使用JDBC的API,相當于是對數(shù)據(jù)庫操作的抽象的擴展,算作橋接模式的抽象部分;而具體的接口實現(xiàn)是由驅動來完成的,驅動這邊自然就相當于橋接模式的實現(xiàn)部分了。而橋接的方式,不再是讓抽象部分持有實現(xiàn)部分,而是采用了類似于工廠的做法,通過DriverManager來把抽象部分和實現(xiàn)部分對接起來,從而實現(xiàn)抽象部分和實現(xiàn)部分解耦。
JDBC的這種架構,把抽象和具體分離開來,從而使得抽象和具體部分都可以獨立擴展。對于應用程序而言,只要選用不同的驅動,就可以讓程序操作不同的數(shù)據(jù)庫,而無需更改應用程序,從而實現(xiàn)在不同的數(shù)據(jù)庫上移植;對于驅動程序而言,為數(shù)據(jù)庫實現(xiàn)不同的驅動程序,并不會影響應用程序。而且,JDBC的這種架構,還合理的劃分了應用程序開發(fā)人員和驅動程序開發(fā)人員的邊界。
對于有些朋友會認為,從局部來看,體現(xiàn)了策略模式,比如在上面的結構中去掉“JDBC的API和基于JDBC的應用程序”這邊,那么剩下的部分,看起來就是一個策略模式的體現(xiàn)。此時的DriverManager就相當于上下文,而各個具體驅動的實現(xiàn)就相當于是具體的策略實現(xiàn),這個理解也不算錯,但是在這里看來,這么理解是比較片面的。
對于這個問題,再次強調(diào)一點:對于設計模式,要從整體結構上、從本質(zhì)目標上、從思想體現(xiàn)上來把握,而不要從局部、從表現(xiàn)、從特例實現(xiàn)上來把握。
3.4 廣義橋接-Java中無處不橋接##
使用Java編寫程序,一個很重要的原則就是“面向接口編程”,說得準確點應該是“面向抽象編程”,由于在Java開發(fā)中,更多的使用接口而非抽象類,因此通常就說成“面向接口編程”了。
接口把具體的實現(xiàn)和使用接口的客戶程序分離開來,從而使得具體的實現(xiàn)和使用接口的客戶程序可以分別擴展,而不會相互影響。使用接口的程序結構如圖所示:
可能有些朋友會覺得,聽起來怎么像是橋接模式的功能呢?沒錯,如果把橋接模式的抽象部分先稍稍簡化一下,暫時不要RefinedAbstraction部分,那么就跟上面的結構圖差不多了。去掉RefinedAbstraction后的簡化的橋接模式結構示意圖如圖所示:
是不是差不多呢?有朋友可能會覺得還是有很大差異,差異主要在:前面接口的客戶程序是直接使用接口對象,不像橋接模式的抽象部分那樣,是持有具體實現(xiàn)部分的接口,這就導致畫出來的結構圖,一個是依賴,一個是聚合關聯(lián)。
請思考它們的本質(zhì)功能,橋接模式中的抽象部分持有具體實現(xiàn)部分的接口,最終目的是什么,還不是需要通過調(diào)用具體實現(xiàn)部分的接口中的方法,來完成一定的功能,這跟直接使用接口沒有什么不同,只是表現(xiàn)形式有點不一樣。再說,前面那個使用接口的客戶程序也可以持有相應的接口對象,這樣從形式上就一樣了。
也就是說,從某個角度來講,橋接模式不過就是對“面向抽象編程”這個設計原則的擴展。正是通過具體實現(xiàn)的接口,把抽象部分和具體的實現(xiàn)分離開來,抽象部分相當于是使用實現(xiàn)部分接口的客戶程序,這樣抽象部分和實現(xiàn)部分就松散耦合了,從而可以實現(xiàn)相互獨立的變化。
這樣一來,幾乎可以把所有面向抽象編寫的程序,都視作是橋接模式的體現(xiàn),至少算是簡化的橋接模式,就算是廣義的橋接吧。而Java編程很強調(diào)“面向抽象編程”,因此,廣義的橋接,在Java中可以說是無處不在。
再舉個大家最熟悉的例子來示例一下。在Java應用開發(fā)中,分層實現(xiàn)算是最基本的設計方式了吧,就拿大家最熟的三層架構來說,表現(xiàn)層、邏輯層和數(shù)據(jù)層,或許有些朋友對它們稱呼的名稱不同,但都是同一回事情。
三層的基本關系是表現(xiàn)層調(diào)用邏輯層,邏輯層調(diào)用數(shù)據(jù)層,通過什么來進行調(diào)用呢?當然是接口了,它們的基本結構如圖所示:
通過接口來進行調(diào)用,使得表現(xiàn)層和邏輯層分離開來,也就是說表現(xiàn)層的變化,不會影響到邏輯層,同理邏輯層的變化不會影響到表現(xiàn)層。這也是同一套邏輯層和數(shù)據(jù)層,就能夠同時支持不同的表現(xiàn)層實現(xiàn)的原因,比如支持Swing或Web方式的表現(xiàn)層。
**在邏輯層和數(shù)據(jù)層之間也是通過接口來調(diào)用,同樣使得邏輯層和數(shù)據(jù)層分離開,使得它們可以獨立的擴展。&&尤其是數(shù)據(jù)層,可能會有很多的實現(xiàn)方式,比如:數(shù)據(jù)庫實現(xiàn)、文件實現(xiàn)等,就算是數(shù)據(jù)庫實現(xiàn),又有針對不同數(shù)據(jù)庫的實現(xiàn),如Oracle、DB2等等。
總之,通過面向抽象編程,三層架構的各層都能夠獨立的擴展和變化,而不會對其它層次產(chǎn)生影響。這正好是橋接模式的功能,實現(xiàn)抽象和實現(xiàn)的分離,從而使得它們可以獨立的變化。當然三層架構不只是在一個地方使用橋接模式,而是至少在兩個地方來使用了橋接模式,一個在表現(xiàn)層和邏輯層之間,一個在邏輯層和數(shù)據(jù)層之間。
下面先分別看看這兩個使用橋接模式的地方的程序結構,然后再綜合起來看看整體的程序結構。先看看邏輯層和數(shù)據(jù)層之間的程序結構,如圖所示:
再看看表現(xiàn)層和邏輯層之間的結構示意,如圖所示:
然后再把它們結合起來,看看結合后的程序結構,如圖所示:
從廣義橋接模式的角度來看,平日熟悉的三層架構其實就是在組合使用橋接模式。從這個圖還可以看出,橋接模式是可以連續(xù)組合使用的,一個橋接模式的實現(xiàn)部分,可以作為下一個橋接模式的抽象部分。如此類推,可以從三層架構擴展到四層、五層、直到N層架構,都可以使用橋接模式來組合。
如果從更本質(zhì)的角度來看,基本上只要是面向抽象編寫的Java程序,都可以視為是橋接模式的應用,都是讓抽象和實現(xiàn)相分離,從而使它們能獨立的變化。不過只要分離的目的達到了,叫不叫橋接模式就無所謂了。
3.5 橋接模式的優(yōu)缺點##
- 分離抽象和實現(xiàn)部分
橋接模式分離了抽象和實現(xiàn)部分,從而極大地提高了系統(tǒng)的靈活性。讓抽象部分和實現(xiàn)部分獨立開來,分別定義接口,這有助于對系統(tǒng)進行分層,從而產(chǎn)生更好的結構化的系統(tǒng)。對于系統(tǒng)的高層部分,只需要知道抽象部分和實現(xiàn)部分的接口就可以了。
- 更好的擴展性
由于橋接模式把抽象和實現(xiàn)部分分離開了,而且分別定義接口,這就使得抽象部分和實現(xiàn)部分可以分別獨立的擴展,而不會相互影響,從而大大的提高了系統(tǒng)的可擴展性。可動態(tài)切換實現(xiàn)。
由于橋接模式把抽象和實現(xiàn)部分分離開了,那么在實現(xiàn)橋接的時候,就可以實現(xiàn)動態(tài)的選擇和使用具體的實現(xiàn),也就是說一個實現(xiàn)不再是固定的綁定在一個抽象接口上了,可以實現(xiàn)運行期間動態(tài)的切換實現(xiàn)。
- 可減少子類的個數(shù)
根據(jù)前面的講述,對于有兩個變化緯度的情況,如果采用繼承的實現(xiàn)方式,大約需要兩個緯度上的可變化數(shù)量的乘積個子類;而采用橋接模式來實現(xiàn)的話,大約需要兩個緯度上的可變化數(shù)量的和個子類。可以明顯地減少子類的個數(shù)`。
3.6 思考橋接模式##
- 橋接模式的本質(zhì)
橋接模式的本質(zhì):分離抽象和實現(xiàn)。
橋接模式最重要的工作就是分離抽象部分和實現(xiàn)部分,這是解決問題的關鍵。只有把抽象和實現(xiàn)分離開了,才能夠讓它們可以獨立的變化;只有抽象和實現(xiàn)可以獨立的變化,系統(tǒng)才會有更好的可擴展性、可維護性。
至于其它的好處,比如:可以動態(tài)切換實現(xiàn)、可以減少子類個數(shù)等。都是把抽象部分和實現(xiàn)部分分離過后,帶來的,如果不把抽象部分和實現(xiàn)部分分離開,那就一切免談了。所以綜合來說,橋接模式的本質(zhì)在于“分離抽象和實現(xiàn)”。
- 對設計原則的體現(xiàn)
(1)橋接模式很好的實現(xiàn)了開閉原則
通常應用橋接模式的地方,抽象部分和實現(xiàn)部分都是可變化的,也就是應用會有兩個變化緯度,橋接模式就是找到這兩個變化,并分別封裝起來,從而合理的實現(xiàn)OCP。
在使用橋接模式的時候,通常情況下,頂層的Abstraction和Implementor是不變的,而具體的Implementor的實現(xiàn),是可變的,由于Abstraction是通過接口來操作具體的實現(xiàn),因此具體的Implementor的實現(xiàn)是可以擴展的,根據(jù)需要可以有多個具體的實現(xiàn)。
同樣的,RefinedAbstraction也是可變的,它繼承并擴展Abstraction,通常在RefinedAbstraction的實現(xiàn)里面,會調(diào)用Abstraction中的方法,通過組合使用來完成更多的功能,這些功能常常是與具體業(yè)務有關系的功能。
(2)橋接模式還很好的體現(xiàn)了:多用對象組合,少用對象繼承
在前面的示例中,如果使用對象繼承來擴展功能,不但讓對象之間有很強的耦合性,而且會需要很多的子類才能完成相應的功能,前面已經(jīng)講述過了,需要兩個緯度上的可變化數(shù)量的乘積個子類。
而采用對象的組合,松散了對象之間的耦合性,不但使每個對象變得簡單和可維護,還大大減少了子類的個數(shù),根據(jù)前面的講述,大約需要兩個緯度上的可變化數(shù)量的和這么多個子類。
- 何時選用橋接模式
建議在如下情況中,選用橋接模式:
如果你不希望在抽象和實現(xiàn)部分采用固定的綁定關系,可以采用橋接模式,來把抽象和實現(xiàn)部分分開,然后在程序運行期間來動態(tài)的設置抽象部分需要用到的具體的實現(xiàn),還可以動態(tài)切換具體的實現(xiàn)。
如果出現(xiàn)抽象部分和實現(xiàn)部分都應該可以擴展的情況,可以采用橋接模式,讓抽象部分和實現(xiàn)部分可以獨立的變化,從而可以靈活的進行單獨擴展,而不是攪在一起,擴展一邊會影響到另一邊。
如果希望實現(xiàn)部分的修改,不會對客戶產(chǎn)生影響,可以采用橋接模式,客戶是面向抽象的接口在運行,實現(xiàn)部分的修改,可以獨立于抽象部分,也就不會對客戶產(chǎn)生影響了,也可以說對客戶是透明的。
如果采用繼承的實現(xiàn)方案,會導致產(chǎn)生很多子類,對于這種情況,可以考慮采用橋接模式,分析功能變化的原因,看看是否能分離成不同的緯度,然后通過橋接模式來分離它們,從而減少子類的數(shù)目。
3.7 相關模式##
- 橋接模式和策略模式
這兩個模式有很大的相似之處。
如果把橋接模式的抽象部分簡化來看,如果暫時不去擴展Abstraction,也就是去掉RefinedAbstraction。橋接模式簡化過后的結構圖參見圖13。再看策略模式的結構圖參見圖17.1。會發(fā)現(xiàn),這個時候它們的結構都類似如圖所示:
通過上面的結構圖,可以體會到橋接模式和策略模式是如此相似。可以把策略模式的Context視做是使用接口的對象,而Strategy就是某個接口了,具體的策略實現(xiàn)那就相當于接口的具體實現(xiàn)。這樣看來的話,某些情況下,可以使用橋接模式來模擬實現(xiàn)策略模式的功能。
這兩個模式雖然相似,也還是有區(qū)別的。最主要的是模式的目的不一樣,策略模式的目的是封裝一系列的算法,使得這些算法可以相互替換;而橋接模式的目的是分離抽象和實現(xiàn)部分,使得它們可以獨立的變化。
- 橋接模式和狀態(tài)模式
由于從模式結構上看,狀態(tài)模式和策略模式是一樣的,這兩個模式的關系也基本上類似于橋接模式和策略模式的關系。
只不過狀態(tài)模式的目的是封裝狀態(tài)對應的行為,并在內(nèi)部狀態(tài)改變的時候改變對象的行為。
- 橋接模式和模板方法模式
這兩個模式有相似之處。
雖然標準的模板方法模式是采用繼承來實現(xiàn)的,但是模板方法也可以通過回調(diào)接口的方式來實現(xiàn),如果把接口的實現(xiàn)獨立出去,那就類似于模板方法通過接口去調(diào)用具體的實現(xiàn)方法了。這樣的結構就和簡化的橋接模式類似了。
可以使用橋接模式來模擬實現(xiàn)模板方法模式的功能。如果在實現(xiàn)Abstraction對象的時候,在里面定義方法,方法里面就是某個固定的算法骨架,也就是說這個方法就相當于模板方法。在模板方法模式里,是把不能確定實現(xiàn)的步驟延遲到子類去實現(xiàn);現(xiàn)在在橋接模式里面,把不能確定實現(xiàn)的步驟委托給具體實現(xiàn)部分去完成,通過回調(diào)實現(xiàn)部分的接口,來完成算法骨架中的某些步驟。這樣一來,就可以實現(xiàn)使用橋接模式來模擬實現(xiàn)模板方法模式的功能了。
使用橋接模式來模擬實現(xiàn)模板方法模式的功能,還有個潛在的好處,就是模板方法也可以很方便的擴展和變化了。在標準的模板方法里面,一個問題就是當模板發(fā)生變化的時候,所有的子類都要變化,非常不方便。而使用橋接模式來實現(xiàn)類似的功能,就沒有這個問題了。
另外,這里只是說從實現(xiàn)具體的業(yè)務功能上,橋接模式可以模擬實現(xiàn)出模板方法模式能實現(xiàn)的功能,并不是說橋接模式和模板方法模式就變成一樣的,或者是橋接模式就可以替換掉模板方法模式了。要注意它們本身的功能、目的、本質(zhì)思想都是不一樣的。
- 橋接模式和抽象工廠模式
這兩個模式可以組合使用。
橋接模式中,抽象部分需要獲取相應的實現(xiàn)部分的接口對象,那么誰來創(chuàng)建實現(xiàn)部分的具體實現(xiàn)對象呢?這就是抽象工廠模式派上用場的地方。也就是使用抽象工廠模式來創(chuàng)建和配置一個特定的具體實現(xiàn)的對象。
事實上,抽象工廠主要是用來創(chuàng)建一系列對象的,如果創(chuàng)建的對象很少,或者是很簡單,還可以采用簡單工廠,可以達到一樣的效果,但是會比抽象工廠來得簡單。
- 橋接模式和適配器模式
這兩個模式可以組合使用。
這兩個模式功能是完全不一樣的,適配器模式的功能主要是用來幫助無關的類協(xié)同工作,重點在解決原本由于接口不兼容而不能一起工作的那些類,使得它們可以一起工作。而橋接模式則重點在分離抽象和實現(xiàn)部分。
所以在使用上,通常在系統(tǒng)設計完成過后,才會考慮使用適配器模式;而橋接模式,是在系統(tǒng)開始的時候就要考慮使用。
雖然功能上不一樣,這兩個模式還是可以組合使用的,比如:已有實現(xiàn)部分的接口,但是有些不太適應現(xiàn)在新的功能對接口的需要,完全拋棄吧,有些功能還用得上,該怎么辦呢?那就使用適配器來進行適配,使得舊的接口能夠適應新的功能的需要。