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