轉載請標明出處: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中非常常用的橋接模式栗子:AbsListView
與ListAdapter
之間的橋接模式。童鞋們可以根據這個栗子體會一下橋接模式的好處。