橋接模式
定義
將抽象部分與它的實現部分分離,使它們都可以獨立地變化。它是一種對象結構型模式,又稱為柄體(Handle and Body)模式或接口(Interface)模式。
上面的定義太簡單了點,并不能很好的解釋什么是橋接模式,看了很多文章覺得還是 LoveLin的解釋最為直接。
試想一下,當我們在繪畫時需要大中小三種型號的畫筆,并且能繪制12種顏色。當我們選擇蠟筆時,為了滿足這個需求,我們需要 12*3=36 支蠟筆。而同樣的情況,如果我們選擇油彩筆時,我們僅需3支不同型號的油彩筆,配合12種不同的顏料就可以了,總共需要 3+12=15 個物品。而且當我們需要增加一種型號的畫筆并且也需要繪制12種顏色,蠟筆需要增加12支,而油彩筆僅需要增加一支不同型號的筆就行。為什么同樣一個需求,選擇不同的畫筆會有不同的結果呢?
這里我們注意到繪畫需求中對畫筆有兩個屬性的需求,型號與顏色,這兩個屬性都是可變可拓展的,選擇蠟筆時每一支蠟筆上這兩個屬性都非常明確,這就導致了兩種屬性有多少種組合,我們就需要多少支蠟筆。而相對的,選擇油彩筆時,這兩個屬性是分開的,油彩筆僅僅具有型號的屬性,而顏色的屬性由顏料提供。
這就是橋接模式最生動的演示,當我們在軟件開發時,某一個類存在兩個獨立變化的維度時,通過橋接模式,可以將這兩個維度分離出來,使兩者可以單獨擴展變化,讓系統更符合“單一職責”原則。
UML圖
上面是橋接模式最常見的結構圖,它包含下面幾個角色:
- Abstraction(抽象類):用于定義抽象類的接口,它一般是抽象類而不是接口,其中定義了一個Implementor(實現類接口)類型的對象并可以維護該對象,它與Implementor之間具有關聯關系,它既可以包含抽象業務方法,也可以包含具體業務方法。
- RefinedAbstraction(擴充抽象類):擴充由Abstraction定義的接口,通常情況下它不再是抽象類而是具體類,它實現了在Abstraction中聲明的抽象業務方法,在RefinedAbstraction中可以調用在Implementor中定義的業務方法。
- Implementor(實現類接口):定義實現類的接口,這個接口不一定要與Abstraction的接口完全一致,事實上這兩個接口可以完全不同,一般而言,Implementor接口僅提供基本操作,而Abstraction定義的接口可能會做更多更復雜的操作。Implementor接口對這些基本操作進行了聲明,而具體實現交給其子類。通過關聯關系,在Abstraction中不僅擁有自己的方法,還可以調用到Implementor中定義的方法,使用關聯關系來替代繼承關系。
- ConcreteImplementor(具體實現類):具體實現Implementor接口,在不同的ConcreteImplementor中提供基本操作的不同實現,在程序運行時,ConcreteImplementor對象將替換其父類對象,提供給抽象類具體的業務操作方法。
PS: 上面的介紹 copy的,解釋的很書面,讀不慣的還是直接看代碼吧 ??
具體到我們前面的例子,Abstraction對應的就是油彩筆的抽象對象,具有型號這個屬性,RefinedAbstraction對應的就是具體三種型號的油彩筆,Implementor對應的就是顏料的抽象對象,ConcreteImplementor對應的就是具體的有不同顏色的顏料。
代碼
//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抽象類的具體實現
public class DefindAbstraction extends Abstraction {
@Override
public void operation() {
impl.operationImpl();
}
}
//Implementor 接口的具體實現
public class ConcreteImplementorA implements Implementor {
@Override
public void operationImpl() {
System.out.println("this is ConcreteImplementorA operation!");
}
}
//Implementor 接口的具體實現
public class ConcreteImplementorB implements Implementor {
@Override
public void operationImpl() {
System.out.println("this is ConcreteImplementorB operation!");
}
}
客戶端調用代碼
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();
}
}
調用結果
this is ConcreteImplementorA operation!
this is ConcreteImplementorB operation!
創建不同維度的具體實現,通過橋接模式配合使用極大的簡化了系統復雜度。
實例
老規矩,還是來一個實例演示一下
某軟件公司欲開發一個數據轉換工具,可以將數據庫中的數據轉換成多種文件格式,例如txt、xml、pdf等格式,同時該工具需要支持多種不同的數據庫。試使用橋接模式對其進行設計。
分析需求可知,這里數據轉換的類型是一個維度,支持的數據庫類型也是一個維度,分離兩個維度進行設計。
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();
}
具體實現如下
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();
}
}
運行結果如下
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
---------------------------------------
小結
在LovaLin的文章中曾提出過如果有兩個以上的維度該怎么解決,相信看完橋接模式的所有代碼后,大家應該有個答案,兩個維度或多個維度,多出的維度都可以分離出一個實現部分,通過抽象部分關聯來解決。
橋接模式極大的提高了提供的擴展性,且大大減少了系統代碼量,分離多個維度更符合“單一職責”原則,且各個維度的擴展都不用修改原系統代碼,符合“開閉原則”。但橋接模式的使用也會一定程度上增加系統的理解與設計難度,需要有一定的經驗才能很好的分別出系統的不同維度。