橋接模式
定義
將抽象部分與它的實現(xiàn)部分分離理逊,使它們都可以獨立地變化肴裙。它是一種對象結(jié)構(gòu)型模式,又稱為柄體(Handle and Body)模式或接口(Interface)模式鸽心。
上面的定義太簡單了點,并不能很好的解釋什么是橋接模式工猜,看了很多文章覺得還是 LoveLin的解釋最為直接。
試想一下菱蔬,當(dāng)我們在繪畫時需要大中小三種型號的畫筆篷帅,并且能繪制12種顏色。當(dāng)我們選擇蠟筆時拴泌,為了滿足這個需求魏身,我們需要 12*3=36 支蠟筆。而同樣的情況蚪腐,如果我們選擇油彩筆時箭昵,我們僅需3支不同型號的油彩筆,配合12種不同的顏料就可以了回季,總共需要 3+12=15 個物品宙枷。而且當(dāng)我們需要增加一種型號的畫筆并且也需要繪制12種顏色掉房,蠟筆需要增加12支茧跋,而油彩筆僅需要增加一支不同型號的筆就行慰丛。為什么同樣一個需求,選擇不同的畫筆會有不同的結(jié)果呢瘾杭?
這里我們注意到繪畫需求中對畫筆有兩個屬性的需求诅病,型號與顏色,這兩個屬性都是可變可拓展的粥烁,選擇蠟筆時每一支蠟筆上這兩個屬性都非常明確贤笆,這就導(dǎo)致了兩種屬性有多少種組合,我們就需要多少支蠟筆讨阻。而相對的芥永,選擇油彩筆時,這兩個屬性是分開的钝吮,油彩筆僅僅具有型號的屬性埋涧,而顏色的屬性由顏料提供。
這就是橋接模式最生動的演示奇瘦,當(dāng)我們在軟件開發(fā)時棘催,某一個類存在兩個獨立變化的維度時,通過橋接模式耳标,可以將這兩個維度分離出來醇坝,使兩者可以單獨擴展變化,讓系統(tǒng)更符合“單一職責(zé)”原則次坡。
UML圖
上面是橋接模式最常見的結(jié)構(gòu)圖呼猪,它包含下面幾個角色:
- Abstraction(抽象類):用于定義抽象類的接口,它一般是抽象類而不是接口砸琅,其中定義了一個Implementor(實現(xiàn)類接口)類型的對象并可以維護該對象宋距,它與Implementor之間具有關(guān)聯(lián)關(guān)系,它既可以包含抽象業(yè)務(wù)方法明棍,也可以包含具體業(yè)務(wù)方法乡革。
- RefinedAbstraction(擴充抽象類):擴充由Abstraction定義的接口,通常情況下它不再是抽象類而是具體類摊腋,它實現(xiàn)了在Abstraction中聲明的抽象業(yè)務(wù)方法沸版,在RefinedAbstraction中可以調(diào)用在Implementor中定義的業(yè)務(wù)方法。
- Implementor(實現(xiàn)類接口):定義實現(xiàn)類的接口兴蒸,這個接口不一定要與Abstraction的接口完全一致视粮,事實上這兩個接口可以完全不同,一般而言橙凳,Implementor接口僅提供基本操作蕾殴,而Abstraction定義的接口可能會做更多更復(fù)雜的操作笑撞。Implementor接口對這些基本操作進行了聲明,而具體實現(xiàn)交給其子類钓觉。通過關(guān)聯(lián)關(guān)系茴肥,在Abstraction中不僅擁有自己的方法,還可以調(diào)用到Implementor中定義的方法荡灾,使用關(guān)聯(lián)關(guān)系來替代繼承關(guān)系瓤狐。
- ConcreteImplementor(具體實現(xiàn)類):具體實現(xiàn)Implementor接口,在不同的ConcreteImplementor中提供基本操作的不同實現(xiàn)批幌,在程序運行時础锐,ConcreteImplementor對象將替換其父類對象,提供給抽象類具體的業(yè)務(wù)操作方法荧缘。
PS: 上面的介紹 copy的皆警,解釋的很書面,讀不慣的還是直接看代碼吧 ??
具體到我們前面的例子截粗,Abstraction對應(yīng)的就是油彩筆的抽象對象信姓,具有型號這個屬性,RefinedAbstraction對應(yīng)的就是具體三種型號的油彩筆桐愉,Implementor對應(yīng)的就是顏料的抽象對象财破,ConcreteImplementor對應(yīng)的就是具體的有不同顏色的顏料。
代碼
//Abstraction抽象類从诲,保持Implementor的引用
public abstract class Abstraction {
protected Implementor impl;
public void setImpl(Implementor impl){
this.impl = impl;
}
//抽象操作方法
public abstract void operation();
}
//Implementor 接口
public interface Implementor {
void operationImpl();
}
//Abstraction抽象類的具體實現(xiàn)
public class DefindAbstraction extends Abstraction {
@Override
public void operation() {
impl.operationImpl();
}
}
//Implementor 接口的具體實現(xiàn)
public class ConcreteImplementorA implements Implementor {
@Override
public void operationImpl() {
System.out.println("this is ConcreteImplementorA operation!");
}
}
//Implementor 接口的具體實現(xiàn)
public class ConcreteImplementorB implements Implementor {
@Override
public void operationImpl() {
System.out.println("this is ConcreteImplementorB operation!");
}
}
客戶端調(diào)用代碼
public class Client {
public static void main(String[] args) {
Abstraction abstraction;
Implementor implementor;
abstraction = new DefindAbstraction();
implementor = new ConcreteImplementorA();
abstraction.setImpl(implementor);
abstraction.operation();
implementor = new ConcreteImplementorB();
abstraction.setImpl(implementor);
abstraction.operation();
}
}
調(diào)用結(jié)果
this is ConcreteImplementorA operation!
this is ConcreteImplementorB operation!
創(chuàng)建不同維度的具體實現(xiàn)左痢,通過橋接模式配合使用極大的簡化了系統(tǒng)復(fù)雜度。
實例
老規(guī)矩系洛,還是來一個實例演示一下
某軟件公司欲開發(fā)一個數(shù)據(jù)轉(zhuǎn)換工具俊性,可以將數(shù)據(jù)庫中的數(shù)據(jù)轉(zhuǎn)換成多種文件格式,例如txt描扯、xml定页、pdf等格式,同時該工具需要支持多種不同的數(shù)據(jù)庫绽诚。試使用橋接模式對其進行設(shè)計典徊。
分析需求可知,這里數(shù)據(jù)轉(zhuǎn)換的類型是一個維度恩够,支持的數(shù)據(jù)庫類型也是一個維度卒落,分離兩個維度進行設(shè)計。
UML圖
代碼
//抽象類 保持一個DaProviderImp的引用
public abstract class DataParser {
protected DataProviderImp dpi;
public void setDpi(DataProviderImp dpi) {
this.dpi = dpi;
}
public abstract void parseData();
}
//DataProviderImp 接口
public interface DataProviderImp {
String readData();
}
具體實現(xiàn)如下
public class TXTDataParser extends DataParser {
@Override
public void parseData() {
String str = dpi.readData();
System.out.println("Parse "+str+" to TXT");
System.out.println("---------------------------------------");
}
}
public class XMLDataParser extends DataParser {
@Override
public void parseData() {
String str = dpi.readData();
System.out.println("Parse "+str+" to XML");
System.out.println("---------------------------------------");
}
}
...
public class OracleDataProvider implements DataProviderImp {
@Override
public String readData() {
System.out.println("Connect DB ---- Oracle");
System.out.println("Read Data from Oracle");
return "Data from Oracle";
}
}
public class MysqlDataProvider implements DataProviderImp {
@Override
public String readData() {
System.out.println("Connect DB ---- Mysql");
System.out.println("Read Data from Mysql");
return "Data from Mysql";
}
}
...
客戶端代碼如下
public class Client {
public static void main(String[] args){
DataParser dataParser;
DataProviderImp dataProviderImp;
dataParser = new TXTDataParser();
dataProviderImp = new OracleDataProvider();
dataParser.setDpi(dataProviderImp);
dataParser.parseData();
dataParser = new XMLDataParser();
dataProviderImp = new MysqlDataProvider();
dataParser.setDpi(dataProviderImp);
dataParser.parseData();
dataParser = new PDFDataParser();
dataProviderImp = new SqlServerDataProvider();
dataParser.setDpi(dataProviderImp);
dataParser.parseData();
}
}
運行結(jié)果如下
Connect DB ---- Oracle
Read Data from Oracle
Parse Data from Oracle to TXT
---------------------------------------
Connect DB ---- Mysql
Read Data from Mysql
Parse Data from Mysql to XML
---------------------------------------
Connect DB ---- SqlServer
Read Data from SqlServer
Parse Data from SqlServer to PDF
---------------------------------------
小結(jié)
在LovaLin的文章中曾提出過如果有兩個以上的維度該怎么解決蜂桶,相信看完橋接模式的所有代碼后儡毕,大家應(yīng)該有個答案,兩個維度或多個維度扑媚,多出的維度都可以分離出一個實現(xiàn)部分腰湾,通過抽象部分關(guān)聯(lián)來解決雷恃。
橋接模式極大的提高了提供的擴展性,且大大減少了系統(tǒng)代碼量费坊,分離多個維度更符合“單一職責(zé)”原則倒槐,且各個維度的擴展都不用修改原系統(tǒng)代碼,符合“開閉原則”葵萎。但橋接模式的使用也會一定程度上增加系統(tǒng)的理解與設(shè)計難度导犹,需要有一定的經(jīng)驗才能很好的分別出系統(tǒng)的不同維度。