Scala 與設計模式(六):Bridge 橋接模式

相信你們都玩過「俄羅斯方塊」吧。java

小羅年幼時最喜歡玩的就是俄羅斯方塊。做爲一個有情懷的程序員,小羅決定嘗試實現這款遊戲。git

玩過俄羅斯方塊的人都會知道,俄羅斯方塊由七種簡單形狀組成:程序員

  • I、J、L、O、S、T、Z

小羅瞭然於心,抄起手中的鍵盤就建立了七個類。github

黑色過於單調,因此小羅又選了三種顏色準備爲這些方塊着色:ide

  • Yellow
  • Blue
  • Red

要實現這樣的需求,最 low 的方法就是爲每種形狀創造全部顏色的版本測試

若是採用這種方案,雙方之間處於強連接,類之間關聯性極強,如要進行擴展,必然致使類結構急劇膨脹:this

若是僅用繼承實現,咱們會創造至少 3 * 7 = 21 個類。spa

當咱們想增長 1 種形狀(或顏色)的時候,就須要新增 3 (或 7)個類。scala

數量爆炸的類 == 差勁的擴展能力 == 爆炸的維護成本設計

從 SOLID 原則來看,以上設計違背了「開放 - 封閉原則」。已知的,在設計類繼承的時候,良好的設計應該是保持引發類變化的因素只有一個,也就是所謂的「單一職責原則」。

那有沒有環保一點的方式呢?讓咱們來看看「橋接模式」是怎麼解決的。

概念

橋接模式的定義比較簡潔:

把事物對象和其具體行爲、具體特徵分離開來,使它們能夠各自獨立的變化。 —— wikipedia

換言之,即 抽象化與實現化解耦,使得兩者能夠獨立變化

根據 GOF 提到的,橋接模式由四部分組成:

  1. 抽象類:定義了一個實現類接口類型的對象並能夠維護該對象。
  2. 擴充抽象類:擴充由抽象類定義的接口,它實現了在抽象類中定義的抽象業務方法,在擴充抽象類中能夠調用在實現類接口中定義的業務方法。
  3. 實現類接口:定義了實現類的接口,實現類接口僅提供基本操做,而抽象類定義的接口可能會作更多更復雜的操做。
  4. 具體實現類:實現了實現類接口而且具體實現它,在不一樣的具體實現類中提供基本操做的不一樣實現,在程序運行時,具體實現類對象將替換其父類對象,提供給客戶端具體的業務操做方法。

Java 實現

在使用橋接模式時,咱們首先應該識別出一個類所具備的兩個獨立變化的維度,將它們設計爲兩個獨立的繼承等級結構,爲兩個維度都提供抽象層,並創建抽象耦合。

即,咱們須要根據實際需求對形狀和顏色進行組合。

既然是組合,接口確定是少不了的,先建立顏色接口(這裏也稱做「橋接口」):

public interface Color {
    public void drawShape(String type);
}
複製代碼

以及各類顏色類:

public class Red implements Color {
    @Override
    public void drawShape(String type) {
        System.out.println("Red " + type +" is drawn");
    }
}

public class Yellow implements Color {
    @Override
    public void drawShape(String type) {
        System.out.println("Yellow " + type +" is drawn");
    }
}

public class Blue implements Color {
    @Override
    public void drawShape(String type) {
        System.out.println("Blue " + type +" is drawn");
    }
}
複製代碼

而後,咱們建立最重要的形狀抽象類:

import Bridge.Java.Color.Color;

public abstract class Shape {
    Color color;

    public void setColor(Bridge.Java.Color.Color color) {
        this.color = color;
    }

    public abstract void draw();
}
複製代碼

一樣建立具體的方塊:

public class ShapeI extends Shape {
    @Override
    public void draw() {
        color.drawShape("ShapeI");
    }
}
複製代碼

測試:

Color red = new Red();
Shape shapeI = new ShapeI();
// 紅色的 I
shapeI.setColor(red);
shapeI.draw();

// 紅色的 L
Shape shapeL = new ShapeJ();
shapeL.setColor(red);
shapeL.draw();
複製代碼

以上,咱們將「形狀」和「顏色」解耦。

bridge-pattern

Hint: 若是你依舊有所疑惑,請回顧最開始的定義:

把事物對象和其具體行爲、具體特徵分離開來,使它們能夠各自獨立的變化。

此時,如需添加新的顏色或形狀,咱們只用實現一個橋接口或者繼承一個抽象類便可。

優缺點

以上,相信你對橋接模式已經有所瞭解。

再咱們來看看它的優缺點。

優勢

  1. 抽象和實現的分離。
  2. 優秀的擴展能力。
  3. 實現細節對客戶透明。

缺點 橋接模式須要創建在你對系統充分的認知下,須要咱們識別出兩個合理的變化維度,因此適用範圍受到限制。

因此你何時該使用橋接模式呢?

適用場景

  1. 正如咱們上方的例子,若是一個場景存在兩個獨立變化的維度,且這兩個維度須要頻繁擴展或變更時,咱們優先考慮橋接模式。

  2. 若是一個系統須要在構件的抽象化角色和具體化角色之間增長更多的靈活性,避免在兩個層次之間創建靜態的繼承聯繫,經過橋接模式可使它們在抽象層創建一個關聯關係。

  3. 對於那些不但願使用繼承或由於多層次繼承致使系統類的個數急劇增長的系統,橋接模式尤其適用。

  4. 其餘

Scala 實現

在 Scala 中,橋接模式的實現與 Java 大同小異,咱們只需將接口關鍵字改成 trait

顏色接口:

trait Color {
   def drawShape(`type`: String)
}
複製代碼

顏色類:

class Red extends Color{
  override def drawShape(`type`: String) = println(s"Red ${`type`} is drawn")
}

class Blue extends Color{
  override def drawShape(`type`: String) = println(s"Blue ${`type`} is drawn")
}

class Yellow extends Color{
  override def drawShape(`type`: String) = println(s"Yellow ${`type`} is drawn")
}
複製代碼

形狀抽象類以及實現類:

abstract class Shape(color: Color) {
   def draw()
}

class ShapeI(color: Color) extends Shape(color){
  override def draw(): Unit = color.drawShape("ShapeI")
}

class ShapeJ(color: Color) extends Shape(color){
  override def draw(): Unit = color.drawShape("ShapeJ")
}

....
複製代碼

也許部分同窗會問:這裏抽象類能夠用 trait 代替嗎?trait擴展性會不會更好?具體仍是參考這裏吧:abstract class 比 trait 好在哪裏?

測試:

object Test extends App{
   new ShapeI(new Blue).draw()
   new ShapeJ(new Red).draw()
}
複製代碼

總結

橋接模式用一種巧妙的方式處理多層繼承存在的問題,用抽象關聯取代了傳統的多層繼承,將類之間的靜態繼承關係轉換爲動態的對象組合關係,使得系統更加靈活,並易於擴展,同時有效控制了系統中類的個數。在系統設計初期,合理利用橋接模式,會讓系統更加優雅。

源碼連接 若有錯誤和講述不恰當的地方還請指出,不勝感激!

相關文章
相關標籤/搜索