想像一下你家裡的電視遙控器,這個遙控器可以控制不同品牌的電視,遙控器本身的功能可能有:開機、關機、調整音量等,甚至隨著需求增加新的按鈕或功能。而電視的功能可能會隨著品牌或型號不同而有所變化。遙控器就像橋接這些不同電視的中介,讓使用者不必考慮每台電視內部的具體細節,只需要知道按下按鈕會有什麼效果。這樣的設計就是「橋接模式」的精髓:把「操作」與「具體實現」分開,讓兩者可以獨立發展。
什麼是橋接模式?
橋接模式是一種結構型設計模式,它的主要目的是將抽象部分與實現部分分離,使它們都可以獨立地變化。簡單來說這個模式可以讓你將「抽象層次」和「具體實作」分離開來,以便兩者可以各自演化而不必互相依賴。當你需要擴充系統時,無論是修改抽象部分還是實作部分,都能簡單且獨立進行。
在程式設計中,當你遇到一個類別因為實作過於複雜或多樣化而導致結構僵化時,你可以透過橋接模式將這些實作細節封裝到一個「實作介面」中,並讓抽象的類別持有該介面,從而達到靈活擴展的效果。橋接模式就像是在兩個獨立變化的維度之間搭建了一座橋樑。這座橋樑使得這兩個維度可以各自獨立地變化,而不會相互影響。
這個模式的基本角色包括:
- 抽象部分(Abstraction):定義了抽象的介面。維護一個對實現者(Implementor)物件的引用。
- 精煉抽象(Refined Abstraction):擴展抽象類,可以新增更多的功能。
- 實現者(Implementor):定義實現類的介面,該介面不一定要與抽象類的介面完全一致。通常只提供基本操作,而抽象類定義的介面可能會做更多更複雜的操作。
- 具體實現(Concrete Implementor):實現實現者介面並提供具體實現。
橋接模式在遊戲開發中的應用
橋接模式經常出現在需要靈活擴展功能的場景中。例如,在圖形繪製的應用中,我們可以有不同的形狀(比如圓形、方形),而每一種形狀可能需要用不同的方式呈現(比如螢幕顯示或列印)。在跨平台GUI系統中,不同作業系統有不同的實現方式。在資料庫程式中,可以切換不同的資料庫系統,對應到不同的資料庫介面,而不影響上層業務邏輯。
這邊我們以一個遊戲開發的例子來說明橋接模式。假設我們正在開發一個角色扮演遊戲,遊戲中有不同的角色(如戰士、法師)和不同的武器(如劍、法杖)。我們希望能夠自由地組合角色和武器,而不是為每種組合都建立一個新的類別。透過橋接模式,我們可以將「角色」與「武器」分開,未來當我們需要新增角色或武器方式時,就不必重新組合所有的類別,達到更好的擴展性。
以下是使用橋接模式實現這個需求的步驟:
首先我們定義武器的抽象介面,1
2
3
4
5
6// 抽象部分 Abstraction
class Weapon {
public:
virtual void use() = 0;
virtual ~Weapon() {}
};
然後我們實現具體的武器類別,1
2
3
4
5
6
7
8
9
10
11
12
13class Sword : public Weapon {
public:
void use() override {
std::cout << "揮舞劍攻擊" << std::endl;
}
};
class Staff : public Weapon {
public:
void use() override {
std::cout << "使用法杖施法" << std::endl;
}
};
接下來我們定義角色的抽象類別,1
2
3
4
5
6
7
8class Character {
protected:
Weapon* weapon;
public:
Character(Weapon* w) : weapon(w) {}
virtual void fight() = 0;
virtual ~Character() {}
};
最後我們實現具體的角色類別,1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Warrior : public Character {
public:
Warrior(Weapon* w) : Character(w) {}
void fight() override {
std::cout << "戰士";
weapon->use();
}
};
class Mage : public Character {
public:
Mage(Weapon* w) : Character(w) {}
void fight() override {
std::cout << "法師";
weapon->use();
}
};
我們可以在客戶端程式碼中自由組合角色和武器,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
27int main() {
Weapon* sword = new Sword();
Weapon* staff = new Staff();
Character* warrior = new Warrior(sword);
Character* mage = new Mage(staff);
warrior->fight(); // 輸出: 戰士揮舞劍攻擊
mage->fight(); // 輸出: 法師使用法杖施法
// 甚至可以讓戰士使用法杖,或讓法師使用劍
Character* warriorWithStaff = new Warrior(staff);
Character* mageWithSword = new Mage(sword);
warriorWithStaff->fight(); // 輸出: 戰士使用法杖施法
mageWithSword->fight(); // 輸出: 法師揮舞劍攻擊
// 釋放記憶體
delete sword;
delete staff;
delete warrior;
delete mage;
delete warriorWithStaff;
delete mageWithSword;
return 0;
}
執行上述程式碼,我們會得到以下輸出:1
2
3
4戰士揮舞劍攻擊
法師使用法杖施法
戰士使用法杖施法
法師揮舞劍攻
在這個遊戲開發的例子中,就像是在角色(Character)和武器(Weapon)之間搭建一座「橋樑」。
橋的一端是抽象部分(Abstraction),這一端是由 Character 類別及其子類(Warrior 和 Mage)所組成。這代表了角色的抽象,定義了角色應該具有的行為(如 fight()
方法)。
橋的另一端是實現部分(Implementation),這一端是由 Weapon 介面及其具體實現(Sword 和 Staff)組成。這代表了武器的實現,定義了武器應該如何被使用(如 use()
方法)。
橋樑本身的核心是在 Character 類中的 Weapon* weapon
成員變數。這個成員變數將抽象部分(角色)和實現部分(武器)連接起來。
透過橋接模式,我們可以新增新的角色(如弓箭手)或新的武器(如弓箭),而不需要修改現有的程式碼,實現了角色和武器可以獨立變化。任何角色都可以使用任何武器,這種組合是在執行時決定的,而不是在編譯時。透過橋接模式還能減少類別數量,如果不使用橋接模式,我們可能需要為每種角色與武器組合建立一個類別(如 WarriorWithSword, MageWithStaff 等),這會導致類別數量的急劇增加。
所以這個橋接模式實際上是一種靈活的連接機制,它允許角色和武器這兩個維度獨立變化,同時又能靈活地組合在一起。這就是橋接模式的核心思想:將抽象部分與其實現部分分離,使它們都可以獨立地變化。
橋接模式的優缺點
橋接模式最大的優點在於它讓抽象與實作分離,因此可以靈活地新增或修改兩者中的任意一方,而不會影響到另一方。這種鬆耦合的設計使得系統的擴展性大大提高,特別是在面對複雜的物件結構或多樣化需求時,它能有效地減少程式碼重複,並且讓系統維護變得更加容易。
橋接模式也並非在所有情況下都是最佳選擇。當你的系統結構簡單、需求較為固定時,這種設計可能會增加不必要的複雜度。另外,由於引入了額外的抽象層,開發初期可能會需要更多的規劃和設計,這在某些情況下會稍微增加開發的難度。
總結
橋接模式特別適合那些需要同時處理多個維度變化的系統。它讓抽象和實作分離,使得系統可以更靈活地演進。在現實世界中,當你面對多重需求的擴展時,橋接模式能讓你輕鬆應對各種變化,避免系統變得難以維護或過於複雜。透過合理的使用橋接模式,你可以在保持系統簡潔的同時,獲得更大的靈活性與可擴展性。