設計模式之橋接模式

轉載請標明出處:http://blog.csdn.net/shensky711/article/details/59185950
本文出自: 【HansChen的博客】


場景問題

發送消息

現在我們要實現這樣一個功能:發送消息。從業務上看,消息又分成普通消息、加急消息和特急消息多種,不同的消息類型,業務功能處理是不一樣的,比如加急消息是在消息上添加“加急”字樣,而特急消息除了添加特急外,還會做一條催促的記錄,多久不完成會繼續催促。從發送消息的手段上看,又有系統內短消息、手機短消息、郵件等等。現在要實現這樣的發送提示消息的功能,該如何實現呢?

不用模式的解決方案

實現簡化版本

先實現一個簡單點的版本:消息只是實現發送普通消息,發送的方式先實現系統內短消息和郵件。其它的功能,等這個版本完成過後,再繼續添加,這樣先把問題簡單化,實現起來會容易一點。由於發送普通消息會有兩種不同的實現方式,爲了讓外部能統一操作,因此,把消息設計成接口,然後由兩個不同的實現類,分別實現系統內短消息方式和郵件發送消息的方式。此時系統結構如下:
這裏寫圖片描述

先來看看消息的統一接口,示例代碼如下:

public interface Message {

    /**
     * 發送消息
     *
     * @param message 要發送的消息內容
     * @param toUser  消息發送的目的人員
     */
    void send(String message, String toUser);
}

再來分別看看兩種實現方式,這裏只是爲了示意,並不會真的去發送Email和站內短消息,先看站內短消息的方式,示例代碼如下:

public class CommonMessageSMS implements Message {

    @Override
    public void send(String message, String toUser) {
        System.out.println("使用站內短消息的方式,發送消息'" + message + "'給" + toUser);
    }
}

同樣的,實現以Email的方式發送普通消息,示例代碼如下:

public class CommonMessageEmail implements Message {

    @Override
    public void send(String message, String toUser) {
        System.out.println("使用Email的方式,發送消息'" + message + "'給" + toUser);
    }
}

實現發送加急消息

上面的實現,看起來很簡單,對不對。接下來,添加發送加急消息的功能,也有兩種發送的方式,同樣是站內短消息和Email的方式。
加急消息的實現跟普通消息不同,加急消息會自動在消息上添加加急,然後再發送消息;另外加急消息會提供監控的方法,讓客戶端可以隨時通過這個方法來了解對於加急消息處理的進度,比如:相應的人員是否接收到這個信息,相應的工作是否已經開展等等。因此加急消息需要擴展出一個新的接口,除了基本的發送消息的功能,還需要添加監控的功能,這個時候,系統的結構如圖所示:
這裏寫圖片描述

先看看擴展出來的加急消息的接口,示例代碼如下:

public interface UrgencyMessage extends Message {

    /**
     * 監控某消息的處理過程
     *
     * @param messageId 被監控的消息的編號
     * @return 包含監控到的數據對象,這裏示意一下,所以用了Object
     */
    Object watch(String messageId);
}

相應的實現方式還是發送站內短消息和Email兩種,同樣需要兩個實現類來分別實現這兩種方式,先看站內短消息的方式,示例代碼如下:

public class UrgencyMessageSMS implements UrgencyMessage {

    @Override
    public void send(String message, String toUser) {
        message = "加急:" + message;
        System.out.println("使用站內短消息的方式,發送消息'" + message + "'給" + toUser);
    }

    @Override
    public Object watch(String messageId) {
        //獲取相應的數據,組織成監控的數據對象,然後返回
        return null;
    }
}

再看看Emai的方式,示例代碼如下:

public class UrgencyMessageEmail implements UrgencyMessage {

    @Override
    public void send(String message, String toUser) {
        message = "加急:" + message;
        System.out.println("使用Email的方式,發送消息'" + message + "'給" + toUser);
    }

    @Override
    public Object watch(String messageId) {
        //獲取相應的數據,組織成監控的數據對象,然後返回
        return null;
    }
}

事實上,在實現加急消息發送的功能上,可能會使用前面發送不同消息的功能,也就是讓實現加急消息處理的對象繼承普通消息的相應實現,這裏爲了讓結構簡單一點,清晰一點,所以沒有這樣做。

有何問題

上面這樣實現,好像也能滿足基本的功能要求,可是這麼實現好不好呢?有沒有什麼問題呢?
我們繼續向下來添加功能實現,爲了簡潔,就不再去進行代碼示意了,通過實現的結構示意圖就可以看出實現上的問題。

繼續添加特急消息的處理

特急消息不需要查看處理進程,只要沒有完成,就直接催促,也就是說,對於特急消息,在普通消息的處理基礎上,需要添加催促的功能。而特急消息、還有催促的發送方式,相應的實現方式還是發送站內短消息和Email兩種,此時系統的結構如圖所示:
這裏寫圖片描述

仔細觀察上面的系統結構示意圖,會發現一個很明顯的問題,那就是:通過這種繼承的方式來擴展消息處理,會非常不方便。
你看,實現加急消息處理的時候,必須實現站內短消息和Email兩種處理方式,因爲業務處理可能不同;在實現特急消息處理的時候,又必須實現站內短消息和Email這兩種處理方式。
這意味着,以後每次擴展一下消息處理,都必須要實現這兩種處理方式,是不是很痛苦,這還不算完,如果要添加新的實現方式呢?繼續向下看吧。

繼續添加發送手機消息的處理方式

如果看到上面的實現,你還感覺問題不是很大的話,繼續完成功能,添加發送手機消息的處理方式
仔細觀察現在的實現,如果要添加一種新的發送消息的方式,是需要在每一種抽象的具體實現裏面,都要添加發送手機消息的處理的。也就是說:發送普通消息、加急消息和特急消息的處理,都可以通過手機來發送。這就意味着,需要添加三個實現。此時系統結構如圖所示:
這裏寫圖片描述

這下能體會到這種實現方式的大問題了吧。

小結一下出現的問題

採用通過繼承來擴展的實現方式,有個明顯的缺點:擴展消息的種類不太容易,不同種類的消息具有不同的業務,也就是有不同的實現,在這種情況下,每個種類的消息,需要實現所有不同的消息發送方式。
更可怕的是,如果要新加入一種消息的發送方式,那麼會要求所有的消息種類,都要加入這種新的發送方式的實現。
要是考慮業務功能上再擴展一下呢?比如:要求實現羣發消息,也就是一次可以發送多條消息,這就意味着很多地方都得修改,太恐怖了。
那麼究竟該如何實現才能既實現功能,又能靈活的擴展呢?

解決方案

橋接模式來解決

用來解決上述問題的一個合理的解決方案,就是使用橋接模式。那麼什麼是橋接模式呢?
橋接模式定義:

將抽象部分和實現部分分離,使它們都可以獨立地變化

應用橋接模式來解決的思路

仔細分析上面的示例,根據示例的功能要求,示例的變化具有兩個維度,一個維度是抽象的消息這邊,包括普通消息、加急消息和特急消息,這幾個抽象的消息本身就具有一定的關係,加急消息和特急消息會擴展普通消息;另一個維度在具體的消息發送方式上,包括站內短消息、Email和手機短信息,這幾個方式是平等的,可被切換的方式。這兩個維度一共可以組合出9種不同的可能性來。
現在出現問題的根本原因,就在於消息的抽象和實現是混雜在一起的,這就導致了,一個維度的變化,會引起另一個維度進行相應的變化,從而使得程序擴展起來非常困難。
要想解決這個問題,就必須把這兩個維度分開,也就是將抽象部分和實現部分分開,讓它們相互獨立,這樣就可以實現獨立的變化,使擴展變得簡單。
橋接模式通過引入實現的接口,把實現部分從系統中分離出去;那麼,抽象這邊如何使用具體的實現呢?肯定是面向實現的接口來編程了,爲了讓抽象這邊能夠很方便的與實現結合起來,把頂層的抽象接口改成抽象類,在裏面持有一個具體的實現部分的實例。
這樣一來,對於需要發送消息的客戶端而言,就只需要創建相應的消息對象,然後調用這個消息對象的方法就可以了,這個消息對象會調用持有的真正的消息發送方式來把消息發送出去。也就是說客戶端只是想要發送消息而已,並不想關心具體如何發送。

模式結構和說明

橋接模式的結構圖:
這裏寫圖片描述

  • Abstraction:抽象部分的接口。通常在這個對象裏面,要維護一個實現部分的對象引用,在抽象對象裏面的方法,需要調用實現部分的對象來完成。這個對象裏面的方法,通常都是跟具體的業務相關的方法。
  • RefinedAbstraction:擴展抽象部分的接口,通常在這些對象裏面,定義跟實際業務相關的方法,這些方法的實現通常會使用Abstraction中定義的方法,也可能需要調用實現部分的對象來完成。
  • Implementor:定義實現部分的接口,這個接口不用和Abstraction裏面的方法一致,通常是由Implementor接口提供基本的操作,而Abstraction裏面定義的是基於這些基本操作的業務方法,也就是說Abstraction定義了基於這些基本操作的較高層次的操作。
  • ConcreteImplementor:真正實現Implementor接口的對象。

橋接模式示例代碼

先看看Implementor接口的定義,示例代碼如下:

public interface Implementor {

    void operationImpl();
}

再看看Abstraction接口的定義,注意一點,雖然說是接口定義,但其實是實現成爲抽象類。示例代碼如下:

public abstract class Abstraction {

    /**
     * 持有一個實現部分的對象
     */
    protected Implementor impl;

    /**
     * 構造方法,傳入實現部分的對象
     *
     * @param impl 實現部分的對象
     */
    public Abstraction(Implementor impl) {
        this.impl = impl;
    }

    public void operation() {
        impl.operationImpl();
    }
}

該來看看具體的實現了,示例代碼如下:

public class ConcreteImplementorA implements Implementor {

    public void operationImpl() {
        //真正的實現
    }
}

另外一個實現,示例代碼如下:

public class ConcreteImplementorB implements Implementor {

    public void operationImpl() {
        //真正的實現
    }
}

最後來看看擴展Abstraction接口的對象實現,示例代碼如下:

public class RefinedAbstraction extends Abstraction {

    public RefinedAbstraction(Implementor impl) {
        super(impl);
    }

    /**
     * 示例操作,實現一定的功能
     */
    public void otherOperation() {

        //實現一定的功能,可能會使用具體實現部分的實現方法,
        //但是本方法更大的可能是使用Abstraction中定義的方法,
        //通過組合使用Abstraction中定義的方法來完成更多的功能
    }
}

使用橋接模式重寫示例

學習了橋接模式的基礎知識過後,該來使用橋接模式重寫前面的示例了。通過示例,來看看使用橋接模式來實現同樣的功能,是否能解決“既能方便的實現功能,又能有很好的擴展性”的問題。
要使用橋接模式來重新實現前面的示例,首要任務就是要把抽象部分和實現部分分離出來,分析要實現的功能,抽象部分就是各個消息的類型所對應的功能,而實現部分就是各種發送消息的方式。
其次要按照橋接模式的結構,給抽象部分和實現部分分別定義接口,然後分別實現它們就可以了。

從簡單功能開始

從相對簡單的功能開始,先實現普通消息和加急消息的功能,發送方式先實現站內短消息和Email這兩種。使用橋接模式來實現這些功能的程序結構如圖所示
這裏寫圖片描述

還是看看代碼實現,會更清楚一些。先看看消息發送器接口,示例代碼如下:

/**
 * 消息發送器
 *
 * @author HansChen
 */
public interface MessageSender {

    /**
     * 發送消息
     *
     * @param message 要發送的消息內容
     * @param toUser  消息發送的目的人員
     */
    void send(String message, String toUser);
}

再看看抽象部分定義的接口,示例代碼如下:

/**
 * 抽象的消息對象
 *
 * @author HansChen
 */
public class AbstractMessageController {

    /**
     * 持有一個實現部分的對象
     */
    MessageSender impl;

    /**
     * 構造方法,傳入實現部分的對象
     *
     * @param impl 實現部分的對象
     */
    AbstractMessageController(MessageSender impl) {
        this.impl = impl;
    }

    /**
     * 發送消息,轉調實現部分的方法
     *
     * @param message 要發送的消息內容
     * @param toUser  消息發送的目的人員
     */
    protected void sendMessage(String message, String toUser) {
        impl.send(message, toUser);
    }
}

看看如何具體的實現發送消息,先看站內短消息的實現吧,示例代碼如下:

/**
 * 以站內短消息的方式發送消息
 *
 * @author HansChen
 */
public class MessageSenderSMS implements MessageSender {

    @Override
    public void send(String message, String toUser) {
        System.out.println("使用站內短消息的方式,發送消息'" + message + "'給" + toUser);
    }
}

再看看Email方式的實現,示例代碼如下:

/**
 * 以Email的方式發送消息
 *
 * @author HansChen
 */
public class MessageSenderEmail implements MessageSender {

    @Override
    public void send(String message, String toUser) {
        System.out.println("使用Email的方式,發送消息'" + message + "'給" + toUser);
    }
}

接下來該看看如何擴展抽象的消息接口了,先看普通消息的實現,示例代碼如下:

public class CommonMessageController extends AbstractMessageController {

    public CommonMessageController(MessageSender impl) {
        super(impl);
    }

    @Override
    public void sendMessage(String message, String toUser) {
        //對於普通消息,什麼都不幹,直接調父類的方法,把消息發送出去就可以了
        super.sendMessage(message, toUser);
    }
}

再看看加急消息的實現,示例代碼如下:

public class UrgencyMessageController extends AbstractMessageController {

    public UrgencyMessageController(MessageSender impl) {
        super(impl);
    }

    @Override
    protected void sendMessage(String message, String toUser) {
        message = "加急:" + message;
        super.sendMessage(message, toUser);
    }

    /**
     * 擴展自己的新功能:監控某消息的處理過程
     *
     * @param messageId 被監控的消息的編號
     * @return 包含監控到的數據對象,這裏示意一下,所以用了Object
     */
    public Object watch(String messageId) {
        //獲取相應的數據,組織成監控的數據對象,然後返回
        return null;
    }
}

添加功能

看了上面的實現,發現使用橋接模式來實現也不是很困難啊,關鍵得看是否能解決前面提出的問題,那就來添加還未實現的功能看看,添加對特急消息的處理,同時添加一個使用手機發送消息的方式。該怎麼實現呢?
很簡單,只需要在抽象部分再添加一個特急消息的類,擴展抽象消息就可以把特急消息的處理功能加入到系統中了;對於添加手機發送消息的方式也很簡單,在實現部分新增加一個實現類,實現用手機發送消息的方式,也就可以了。
這麼簡單?好像看起來完全沒有了前面所提到的問題。的確如此,採用橋接模式來實現過後,抽象部分和實現部分分離開了,可以相互獨立的變化,而不會相互影響。因此在抽象部分添加新的消息處理,對發送消息的實現部分是沒有影響的;反過來增加發送消息的方式,對消息處理部分也是沒有影響的。

接着看看代碼實現,先看看新的特急消息的處理類,示例代碼如下:

public class SpecialUrgencyMessageController extends AbstractMessageController {

    public SpecialUrgencyMessageController(MessageSender impl) {
        super(impl);
    }

    @Override
    protected void sendMessage(String message, String toUser) {
        message = "特急:" + message;
        super.sendMessage(message, toUser);
    }

    public void hurry(String messageId) {
        //執行催促的業務,發出催促的信息
    }
}

再看看使用手機短消息的方式發送消息的實現,示例代碼如下:

public class MessageSenderMobile implements MessageSender {

    @Override
    public void send(String message, String toUser) {
        System.out.println("使用手機的方式,發送消息'" + message + "'給" + toUser);
    }
}

測試一下功能

看了上面的實現,可能會感覺得到,使用橋接模式來實現前面的示例過後,添加新的消息處理,或者是新的消息發送方式是如此簡單,可是這樣實現,好用嗎?寫個客戶端來測試和體會一下,示例代碼如下:

public class Client {

    public static void main(String[] args) {
        //創建具體的實現對象
        MessageSender impl = new MessageSenderSMS();

        //創建一個普通消息對象
        AbstractMessageController controller = new CommonMessageController(impl);
        controller.sendMessage("請喝一杯茶", "小李");

        //創建一個緊急消息對象
        controller = new UrgencyMessageController(impl);
        controller.sendMessage("請喝一杯茶", "小李");

        //創建一個特急消息對象
        controller = new SpecialUrgencyMessageController(impl);
        controller.sendMessage("請喝一杯茶", "小李");


        //把實現方式切換成手機短消息,然後再實現一遍
        impl = new MessageSenderMobile();
        controller = new CommonMessageController(impl);
        controller.sendMessage("請喝一杯茶", "小李");

        controller = new UrgencyMessageController(impl);
        controller.sendMessage("請喝一杯茶", "小李");

        controller = new SpecialUrgencyMessageController(impl);
        controller.sendMessage("請喝一杯茶", "小李");
    }
}

運行結果如下:

使用站內短消息的方式,發送消息'請喝一杯茶'給小李
使用站內短消息的方式,發送消息'加急:請喝一杯茶'給小李
使用站內短消息的方式,發送消息'特急:請喝一杯茶'給小李
使用手機的方式,發送消息'請喝一杯茶'給小李
使用手機的方式,發送消息'加急:請喝一杯茶'給小李
使用手機的方式,發送消息'特急:請喝一杯茶'給小李

前面三條是使用的站內短消息,後面三條是使用的手機短消息,正確的實現了預期的功能。看來前面的實現應該是正確的,能夠完成功能,且能靈活擴展。

廣義橋接-Java中無處不橋接

使用Java編寫程序,一個很重要的原則就是“面向接口編程”,說得準確點應該是“面向抽象編程”,由於在Java開發中,更多的使用接口而非抽象類,因此通常就說成“面向接口編程”了。接口把具體的實現和使用接口的客戶程序分離開來,從而使得具體的實現和使用接口的客戶程序可以分別擴展,而不會相互影響。

橋接模式中的抽象部分持有具體實現部分的接口,最終目的是什麼,還不是需要通過調用具體實現部分的接口中的方法,來完成一定的功能,這跟直接使用接口沒有什麼不同,只是表現形式有點不一樣。再說,前面那個使用接口的客戶程序也可以持有相應的接口對象,這樣從形式上就一樣了。

也就是說,從某個角度來講,橋接模式不過就是對“面向抽象編程”這個設計原則的擴展。正是通過具體實現的接口,把抽象部分和具體的實現分離開來,抽象部分相當於是使用實現部分接口的客戶程序,這樣抽象部分和實現部分就鬆散耦合了,從而可以實現相互獨立的變化。

這樣一來,幾乎可以把所有面向抽象編寫的程序,都視作是橋接模式的體現,至少算是簡化的橋接模式,就算是廣義的橋接吧。而Java編程很強調“面向抽象編程”,因此,廣義的橋接,在Java中可以說是無處不在。

橋接模式在Android中的應用

如果各位童鞋看到這裏仍然對橋接模式還是不太清楚,在這裏給大家舉個在Android中非常常用的橋接模式栗子:AbsListViewListAdapter之間的橋接模式。童鞋們可以根據這個栗子體會一下橋接模式的好處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章