設計模式系列文章:

秒懂Java代理與動態代理模式 秒懂設計模式之建造者模式(Builder Pattern)

秒懂設計模式之工廠方法模式(Factory Method Pattern)

秒懂設計模式之抽象工廠模式(Abstract Factory Pattern)

秒懂設計模式之策略模式(Strategy Pattern)

前言

人在IT江湖飄,不懂設計模式咋裝X?

橋接模式在日常開發中不是特別常用,主要是因為上手難度較大,但是對於理解面向對象設計有非常大的幫助。

定義

橋接模式是將抽象部分與它的實現部分分離,使它們都可以獨立地變化。它是一種對象結構型模式,又稱為柄體(Handle and Body)模式或介面(Interfce)模式。

使用場景

我們大家都熟悉,顧名思義就是用來將河的兩岸聯繫起來的。而此處的橋是用來將兩個獨立的結構聯繫起來,而這兩個被聯繫起來的結構可以獨立的變化,所有其他的理解只要建立在這個層面上就會比較容易。

下面是一些官方的說明,比較晦澀,必須等你有一定的經驗後才能理解: 1. 如果一個系統需要在抽象化和具體化之間增加更多的靈活性,避免在兩個層次之間建立靜態的繼承關係,通過橋接模式可以使它們在抽象層建立一個關聯關係。

  1. 「抽象部分」和「實現部分」可以以繼承的方式獨立擴展而互不影響,在程序運行時可以動態將一個抽象化子類的對象和一個實現化子類的對象進行組合,即系統需要對抽象化角色和實現化角色進行動態耦合。
  2. 一個類存在兩個(或多個)獨立變化的維度,且這兩個(或多個)維度都需要獨立進行擴展。

  3. 對於那些不希望使用繼承或因為多層繼承導致系統類的個數急劇增加的系統,橋接模式尤為適用。

如何實現

業務場景

在講策略模式的時候,王二狗和牛翠花不是要到天津之眼去約炮,不,約會嘛,兩人到那後先去星巴克喝咖啡了,星巴克提供了多種選擇,從容量上說有大杯,中杯,小杯,從口味上說有原味,加糖,這可難為了有選擇恐懼症的牛翠花,半天點不出來,後面的人都開始罵娘了,王二狗也只能陪著笑臉道歉。 其實被難為的除了牛翠花,還有給星巴克做訂單系統的外包公司的程序員林蛋大。一開始提需求的時候星巴克說我們只有正常杯(中杯),原味和加糖這幾種選擇,人家林蛋大也是有兩年工作經驗的碼農,這需求不在話下,蛋大還想到了要面對抽象編程。

首先定義一個點咖啡介面,裡面有一個下單方法,至於點哪種口味的咖啡,就由其子類去決定,完美,蛋大好牛逼啊!

public interface ICoffee {
void orderCoffee(int count);
}

原味咖啡類

public class CoffeeOriginal implements ICoffee {
@Override
public void orderCoffee(int count) {
System.out.println(String.format("原味咖啡%d杯",count));
}
}

加糖咖啡類

public class CoffeeWithSugar implements ICoffee {
@Override
public void orderCoffee(int count) {
System.out.println(String.format("加糖咖啡%d杯",count));
}
}

搞定收工,王者榮耀搞起!項目經理突然過來了:蛋大啊,客戶那邊說了,他們準備加兩個容量規格的咖啡,大杯和小杯,你改一下。林蛋大:what?尼瑪為什麼不早說,老子剛寫完。胸中雖有萬千牢騷,還不得平復一下心情去改代碼,何必呢?蛋大心中暗喜,幸好老子是面向抽象編程的,對應加幾個實現類不就得了?

//中杯加糖
public class CoffeeWithSugar implements ICoffee {
@Override
public void orderCoffee(int count) {
System.out.println(String.format("中杯加糖咖啡%d杯",count));
}
}
//大杯加糖
public class LargeCoffeeWithSugar implements ICoffee {
@Override
public void orderCoffee(int count) {
System.out.println(String.format("大杯加糖咖啡%d杯",count));
}
}
。。。

加著加著蛋大慌了,共需要3x2=6個類啊,大杯原味和加糖,中杯原味和加糖,小杯原味和加糖。過段時間萬一那二筆客戶又要出加奶,加蜂蜜等等口味,說不定還有迷你杯,女神杯等等規格的咖啡,那我這邊的類不就爆炸了嗎?看來的去找個設計模式了。。。

此場景橋接模式正合適,這裡有兩個變化維度,咖啡的容量和口味,而且都需要獨立變化。如果使用繼承的方式,隨著變化類就會急劇的增加。你可以將容量理解為抽象部分,而口味理解為實現部分,這兩個部分需要橋接。

使用橋接模式

橋接模式UML 圖如下

橋樑模式所涉及的角色有:

  • 抽象化(Abstraction)角色:抽象化給出的定義,並保存一個對實現化對象的引用。
  • 修正抽象化(RefinedAbstraction)角色:擴展抽象化角色,改變和修正父類對抽象化的定義。
  • 實現化(Implementor)角色:這個角色給出實現化角色的介面,但不給出具體的實現。必須指出的是,這個介面不一定和抽象化角色的介面定義相同,實際上,這兩個介面可以非常不一樣。實現化角色應當只給出底層操作,而抽象化角色應當只給出基於底層操作的更高一層的操作。
  • 具體實現化(ConcreteImplementor)角色:這個角色給出實現化角色介面的具體實現。

抽象化角色就像是一個水杯的手柄,而實現化角色和具體實現化角色就像是水杯的杯身。手柄控制杯身,這就是此模式別名「柄體」的來源。

林蛋大查了半天不知道使用哪個設計模式,只能求助前輩王二狗了。電話通了:狗哥,我這有個問題。。。王二狗:我這正解決終身大事呢,沒工夫理你,你去調查一下橋接模式。林蛋大:不愧是大神,改天請你和嫂子喫飯,先掛啦。掛電話後,蛋大就開始用橋接模式重構代碼了。

蛋大分析了當前業務場景,認為可以將咖啡的容量作為抽象化Abstraction,而咖啡口味為實現化Implementor

第一步:創建抽象化部分:

//抽象化Abstraction
public abstract class Coffee {
protected ICoffeeAdditives additives;
public Coffee(ICoffeeAdditives additives){
this.additives=additives;
}
public abstract void orderCoffee(int count);
}

我們可以看到,Coffee持有了ICoffeeAdditives 引用,ICoffeeAdditives 的實例是通過構造函數注入的,這個過程就是我們所說的橋接過程。我們通過這個引用就可以調用ICoffeeAdditives的方法,進而將Coffee的行為與ICoffeeAdditives的行為通過orderCoffee()方法而組合起來。

下面是一個對抽象化修正的一個類,裡面增加了一個品控的方法

//RefinedAbstraction
public abstract class RefinedCoffee extends Coffee {
public RefinedCoffee(ICoffeeAdditives additives) {
super(additives);
}
public void checkQuality(){
Random ran=new Random();
System.out.println(String.format("%s 添加%s",additives.getClass().getSimpleName(),ran.nextBoolean()?"太多":"正常"));
}
}

第二步:創建實現化部分

public interface ICoffeeAdditives {
void addSomething();
}
//加奶
public class Milk implements ICoffeeAdditives {
@Override
public void addSomething() {
System.out.println("加奶");
}
}
//加糖
public class Sugar implements ICoffeeAdditives {
@Override
public void addSomething() {
System.out.println("加糖");
}
}

第三步:客戶端調用

public static void main(String[] args) {
//點兩杯加奶的大杯咖啡
RefinedCoffee largeWithMilk=new LargeCoffee(new Milk());
largeWithMilk.orderCoffee(2);
largeWithMilk.checkQuality();
}

輸出結果:

加奶
大杯咖啡2杯
Milk 添加太多

通過使用橋接模式,就使得咖啡的容量和口味這兩個維度可以獨立變化,互不幹擾。林蛋大終於完成了代碼的重構,回頭一看王者榮耀由於惡意掛機被扣除15個信用分,暫時不能進行排位賽,無奈只能再去鞏固一下橋接模式了。

優缺點

優點:

  • 分離抽象介面及其實現部分。橋接模式使用「對象間的關聯關係」解耦了抽象和實現之間固有的綁定關係,使得抽象和實現可以沿著各自的維度來變化。所謂抽象和實現沿著各自維度的變化,也就是說抽象和實現不再在同一個繼承層次結構中,而是「子類化」它們,使它們各自都具有自己的子類,以便任何組合子類,從而獲得多維度組合對象。
  • 在很多情況下,橋接模式可以取代多層繼承方案,多層繼承方案違背了「單一職責原則」,復用性較差,且類的個數非常多,橋接模式是比多層繼承方案更好的解決方法,它極大減少了子類的個數。
  • 橋接模式提高了系統的可擴展性,在兩個變化維度中任意擴展一個維度,都不需要修改原有系統,符合「開閉原則」。

缺點:

  • 橋接模式的使用會增加系統的理解與設計難度,由於關聯關係建立在抽象層,要求開發者一開始就針對抽象層進行設計與編程。
  • 橋接模式要求正確識別出系統中兩個獨立變化的維度,因此其使用範圍具有一定的侷限性,如何正確識別兩個獨立維度也需要一定的經驗積累。

總結

經過此次事件,林蛋大對王二狗的敬佩又多了幾分,二狗哥真乃神人也,不僅代碼寫的好,還能找到女朋友!

設計模式值得你刻意練習!

參考文章: 1:java設計模式之橋接模式


推薦閱讀:
相關文章