Home [閱讀筆記][Design Pattern] Ch22.橋接模式(Bridge)
Post
Cancel

[閱讀筆記][Design Pattern] Ch22.橋接模式(Bridge)

橋接模式(Bridge)

橋接模式(Bridge),將抽象部分與它的實現部分分離,使它們都可以獨立地變化。

兩個抽象類別之間,就像一座橋,有一個聚合線。

什麼叫做抽象與實現分離?

什麼叫做抽象與實現分離,這並不是說,讓抽象類別與其衍生類別分離,因為這沒有任何意義。

實現指是抽象類別和它的衍生類別用來實現自己的物件。

比如,手機可以按照品牌來分類,也可以按照功能來分類:
(這樣的繼承結構,如果不斷地增加新品牌或新功能,類別會越來越多,面對新的需求時,改動過大並影響其他類別的不合理情況。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
手機品牌(按照品牌來分類)
    手機品牌M
        手機品牌M遊戲
        手機品牌M通訊錄
    手機品牌S
    手機品牌S遊戲
    手機品牌S通訊錄

手機軟體(按照功能來分類)
    遊戲
        手機品牌M遊戲
        手機品牌S遊戲
    通訊錄
        手機品牌M通訊錄
        手機品牌S通訊錄

由於實現的方式有很多種,橋接模式的核心意圖就是把這些實現獨立出來,讓它們各自地變化。這就使得每種實現的變化不會影響其他實現,從而達到應對變化的目的。

1
2
3
4
5
6
手機品牌
    品牌N
    品牌S
手機軟體
    通訊錄
    遊戲

結構圖

1
2
3
4
5
6
7
8
9
10
11
抽象 Abstraction
    + Operation()
        RefinedAbstraction 被提煉的抽象
        + Operation()

實現 Implementor
    + OperationImp()
        ConcreteImplementorA 具體實現A
        + OperationImp()
        ConcreteImplementorB 具體實現B
        + OperationImp()

基本程式碼

實現 Implementor

1
2
3
abstract class Implementor {
    public abstract void Operation();
}

衍生類別 ConcreteImplementorA/ConcreteImplementorB

1
2
3
4
5
6
7
8
9
10
11
12
13
//具體實現A
class ConcreteImplementorA: Implementor {
    public override void Operation() {
        Console.WriteLine("具體實現A的方法執行");
    }
}

//具體實現B
class ConcreteImplementorB: Implementor {
    public override void Operation() {
        Console.WriteLine("具體實現B的方法執行");
    }
}

Abstraction

1
2
3
4
5
6
7
8
9
10
class Abstraction {
    protected Implementor implementor;
    public void SetImplementor(Implementor implementor) {
        this.implementor = implementor;
    }

    public virtual Operation() {
        implementor.Operation();
    }
}

RefinedAbstraction 被提煉的抽象

1
2
3
4
5
class RefinedAbstraction: Abstraction {
    public override Operation() {
        implementor.Operation();
    }
}

用戶端

1
2
3
4
5
6
Abstraction ab = new RefinedAbstraction();
ab.SetImplementor(new ConcreteImplementorA());
ab.Operation();

ab.SetImplementor(new ConcreteImplementorB());
ab.Operation();

何時用?

在發現我們需要多角度去分類實現物件,而只用繼承會造成大量的類別增加,不能滿足「開放-封閉原則」時,就應該要考慮用「橋接模式」了。

緊耦合

很多情況用繼承會帶來麻煩。比如,物件的繼承關係是在編譯時就定義好了,所以無法在執行時改變從父類別繼承的實現。子類別的實現與父類別有非常緊密的依賴關係,以至於父類別實現中的任何變化必然會導致子類別發生變化。當你需要複用子類別時,如果繼承下來的實現不適合解決新的問題,則父類別必須重寫或被其他更適合的類別替換。這種依賴關係限制了靈活性,也限制了複用性。

在物件導向設計中,我們還有一個很重要的設計原則,那就是「合成/聚合複用原則」。即優先使用物件合成/聚合,而不是類別繼承。

盲目使用繼承會造成麻煩,而其本質原因是:繼承是一種強耦合的結果。父類別變,子類別就必須要變。
所以在用繼承時,一定要是在is-a的關係時再考慮使用,而不是任何時候都去使用。

合成/聚合複用原則(CARP)

合成/聚合複用原則(CARP),儘量使用合成/聚合,儘量不要使用類別繼承。

  • 合成(Composition,也有翻譯成組合):表示一種「弱」的「擁有」關係,表現的是A物件可以包含B物件,但B物件不是A物件的一部分。
  • 聚合(Aggregation):是一種「強」的「擁有」關係,表現了嚴格的部分和整體的關係,部分和整體的生命周期一樣。

比如說,大雁有兩個翅膀,翅膀與大雁是部分和整體的關係,並且牠們的生命周期是相同的,於是大雁和翅膀就是「合成關係」。

而大雁是群居動物,所以每隻大雁都是屬於一個雁群,一個雁群可以有多隻大雁,所以大雁和雁群是「聚合關係」。

1
2
翅膀 → 大雁 → 雁群
   合成   聚合

好處

合成/聚合複用原則(CARP)的好處是,優先使用物件的合成/聚合將有助於你維持每個類別被封裝,並集中在單個任務上。這樣類別和類別繼承層次會保持較小規模,並且不太可能增長為不可控制的龐然大物。

比如,手機和PC、手機和軟體(手機品牌和手機軟體),需要學會用物件的職責,而不是結構來考慮問題。

緊耦合的程式(橋接模式(Bridge))

結構

1
2
3
4
5
6
7
手機品牌
    手機品牌N
    手機品牌M
手機軟體
    通訊錄
    遊戲
    音樂播放

程式

手機軟體-抽象類別、遊戲/通訊錄具體類別

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//手機軟體-抽象類別
abstract class HandsetSoft {
    public abstract void Run();
}

//遊戲
class HandsetGame: HandsetSoft {
    public override void Run() {
        Console.WriteLine("執行手機遊戲");
    }
}

//通訊錄
class HandsetAddressList: HandsetSoft {
    public override void Run() {
        Console.WriteLine("執行手機通訊錄");
    }
}

手機品牌-抽象類別、品牌N/品牌M具體類別

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//手機品牌-抽象類別
abstract class HandsetBrand {
    //宣告一個受保護的手機軟體變數
    protected HandsetSoft soft;

    //設定手機軟體
    //品牌需要關注軟體,所以可在機器中安裝軟體(設定手機軟體),以備執行
    public void SetHandsetSoft(HandsetSoft soft) {
        this.soft = soft;
    }

    //執行
    public abstract void Run();
}

//手機品牌N
class HandsetBrandN: HandsetBrand {
    public override void Run() {
        soft.Run();
    }
}

//手機品牌M
class HandsetBrandM: HandsetBrand {
    public override void Run() {
        soft.Run();
    }
}

用戶端程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void Main()
{
    //手機品牌N
    HandsetBrand phone = new HandsetBrandN();	
    phone.SetHandsetSoft(new HandsetGame()); //設定遊戲
    phone.Run();//執行
    phone.SetHandsetSoft(new HandsetAddressList());//設定通訊錄
    phone.Run();//執行

    //手機品牌M
    phone = new HandsetBrandM();
    phone.SetHandsetSoft(new HandsetGame());//設定遊戲
    phone.Run();//執行
    phone.SetHandsetSoft(new HandsetAddressList());//設定通訊錄
    phone.Run();//執行
} 

新增需求

如果現在要增加功能或手機品牌,那麼只要增加這個類別就行了,不會影響其他任何類別。類別的個數增加也只是一個,不會影響其他類別的改動。

  • MP3音樂播放功能
  • 手機品牌S
1
2
3
4
5
6
7
8
9
10
11
12
13
//MP3音樂播放功能
class HandsetMP3: HandsetSoft {
    public override void Run() {
        Console.WriteLine("執行手機MP3音樂播放");
    }
}

//手機品牌S
class HandsetBrandS: HandsetBrand {
    public override void Run() {
        soft.Run();
    }
}

這顯然也符合「開放-封閉原則」,這樣的設計不會修改原來的程式碼,而只是擴展類別就行了。

This post is licensed under CC BY 4.0 by the author.

[閱讀筆記][Design Pattern] Ch21.獨體模式(Singleton)

[閱讀筆記][Design Pattern] Ch23.命令模式(Command)