首頁 > 軟體

.Net結構型設計模式之橋接模式(Bridge)

2022-05-25 18:01:02

一、動機(Motivation)

在很多遊戲場景中,會有這樣的情況:【裝備】本身會有的自己固有的邏輯,比如槍支,會有型號的問題,同時現在很多的遊戲又在不同的媒介平臺上執行和使用,這樣就使得遊戲的【裝備】具有了兩個變化的維度——一個變化的維度為“平臺的變化”,另一個變化的維度為“型號的變化”。如果我們要寫程式碼實現這款遊戲,難道我們針對每種平臺都實現一套獨立的【裝備】嗎?複用在哪裡?如何應對這種“多維度的變化”?如何利用物件導向技術來使得【裝備】可以輕鬆地沿著“平臺”和“型號”兩個方向變化,而不引入額外的複雜度?

二、意圖(Intent)

將抽象部分與實現部分分離,使它們都可以獨立地變化。

橋模式不能只是認為是抽象和實現的分離,它其實並不僅限於此。其實兩個都是抽象的部分,更確切的理解,應該是將一個事物中多個維度的變化分離。

三、結構(Structure)

其中imp的地方就是一個組合。Abstraction就是我們例子中的Tank,它的子類RefinedAbstraction就是T50等型號。Implementor是TankPlatformImplementation類,ConcreteImplementorA和ConcreteImplementorB分別是PCTankImplementation和MobileTankImplementation。

整個設計模式的關鍵就是組合的使用。

四、模式的組成

橋接模式的結構包括Abstraction、RefinedAbstraction、Implementor、ConcreteImplementorA和ConcreteImplementorB五個部分,其中: 
(1)、抽象化角色(Abstraction):抽象化給出的定義,並儲存一個對實現化物件(Implementor)的參照。 
(2)、修正抽象化角色(Refined Abstraction):擴充套件抽象化角色,改變和修正父類別對抽象化的定義。 
(3)、實現化角色(Implementor):這個角色給出實現化角色的介面,但不給出具體的實現。必須指出的是,這個介面不一定和抽象化角色的介面定義相同,實際上,這兩個介面可以非常不一樣。實現化角色應當只給出底層操作,而抽象化角色應當只給出基於底層操作的更高一層的操作。 
(4)、具體實現化角色(Concrete Implementor):這個角色給出實現化角色介面的具體實現。 
在橋接模式中,兩個類Abstraction和Implementor分別定義了抽象與行為型別的介面,通過呼叫兩介面的子類實現抽象與行為的動態組合。 

五、橋接模式的具體程式碼實現

假如我們需要開發一個同時支援PC和手機端的坦克遊戲,遊戲在PC和手機上功能都一樣,都有同樣的型別,面臨同樣的功能需求變化,比如坦克可能有很多種不同的型號:T50,T75,T90……

對於其中的坦克設計,我們可能很容易設計出來一個Tank的抽象基礎類別,然後各種不同型號的Tank繼承自該類;

這一步實現一點問題也沒有,也符合開閉原則,繼續往下看。

另外的變化原因

但是PC和手機上的圖形繪製、聲效、操作等實現完全不同……因此對於各種型號的坦克,都要提供各種不同平臺上的坦克實現:

我們一般會設計成這樣,但是這樣看很怪,這樣的設計會帶來很多問題:有很多重複程式碼,類的結構過於複雜,難以維護,最致命的是引入任何新平臺,比如在TV上的Tank遊戲,都會讓整個類層級結構複雜化。我們做軟體,修改的時候,修改的越少越好,說明隔離的比較好。

public abstract class TankModel
{
    protected TankPlatformImplementation _tankImp;

    public TankModel(TankPlatformImplementation tankImp)
    {
        _tankImp = tankImp;
    }
    public abstract void Run();
}

public abstract class TankPlatformImplementation
{
    public abstract void MoveTankTo(int x, int y);
    public abstract void DrawTank();
    public abstract void Attack();
}

/// 

/// PC坦克
/// 
public class PCTankImplatation : TankPlatformImplementation
{
    string _tankModel;
    public PCTankImplatation(string tankModel)
    {
        _tankModel = tankModel;
    }
    /// 

    /// 繪製坦克
    /// 
    public override void DrawTank()
    {
        Console.WriteLine(_tankModel + "PC坦克繪製成功!");
    }
    /// 

    /// 坦克移動
    /// 
    /// x座標
    /// y座標
    public override void MoveTankTo(int x, int y)
    {
        Console.WriteLine(_tankModel + "PC坦克已經移動到了座標(" + x + "," + y + ")處");
    }
    /// 

    /// 攻擊
    /// 
    public override void Attack()
    {
        Console.WriteLine(_tankModel + "PC坦克開始攻擊");
    }
}
/// 

/// T50型號坦克
/// 
public class T50 : TankModel
{
    public T50(TankPlatformImplementation tankImp) : base(tankImp) { }
    public override void Run()
    {
        _tankImp.DrawTank();
        _tankImp.MoveTankTo(100, 100);
        _tankImp.Attack();
    }
}

/// 

/// 使用者端呼叫
/// 
public class App
{
    void Main(string[] agrs)
    {
        T50 t = new T50(new PCTankImplatation("T50"));
        t.Run();
    }
}

使用了橋接模式後,當需求發生變化後就很容易來應對了,假如現在又多了一種T60型號的坦克,並且新增了一個手機平臺。只需要新增T60型號的具體類和手機平臺具體類即可,如下:

/// 
/// 手機坦克
/// 
public class MobileTankImplatation : TankPlatformImplementation
{
    string _tankModel;
    public MobileTankImplatation(string tankModel)
    {
        _tankModel = tankModel;
    }
    /// 

    /// 繪製坦克
    /// 
    public override void DrawTank()
    {
        Console.WriteLine(_tankModel+"Mobile坦克繪製成功!");
    }
    /// 

    /// 坦克移動
    /// 
    /// x座標
    /// y座標
    public override void MoveTankTo(int x, int y)
    {
        Console.WriteLine(_tankModel+"Mobile坦克已經移動到了座標(" + x + "," + y + ")處");
    }
    /// 

    /// 攻擊
    /// 
    public override void Attack()
    {
        Console.WriteLine(_tankModel+"Mobile坦克開始攻擊");
    }
}
/// 

/// T60型號坦克
/// 
public class T60 : TankModel
{
    public T60(TankPlatformImplementation tankImp) : base(tankImp) { }
    public override void Run()
    {
        _tankImp.DrawTank();
        _tankImp.MoveTankTo(400, 100);
        _tankImp.Attack();
    }
}

新增這兩個類後現在我們有T50型號、 T60型號 、PC平臺、手機平臺,雖然只新增了兩個類,但現在有了四種組合,看使用者端程式碼的呼叫:

/// 
/// 使用者端呼叫
/// 
public class App
{
    void Main(string[] agrs)
    {
       //T50在PC上
        T50 t50PC = new T50(new PCTankImplatation("T50"));
       t50PC.Run();
       //T50在Mobile上
        T50 t50Mobile = new T50(new MobileTankImplatation("T50"));
       t50Mobile.Run();
       //T60在PC上
        T60 t60PC = new T60(new PCTankImplatation("T60"));
       t60PC.Run();
       //T60在Mobile上
        T60 t60Mobile = new T60(new MobileTankImplatation("T60"));
       t60Mobile.Run();
    }
}

六、橋接模式的實現要點:

1.Bridge模式使用“物件間的組合關係”解耦了抽象和實現之間固有的繫結關係,使得抽象和實現可以沿著各自的維度來變化。 
2.所謂抽象和實現沿著各自維度的變化,即“子類化”它們,得到各個子類之後,便可以任意組合它們,從而獲得不同平臺上的不同型號。 
3.Bridge模式有時候類似於多繼承方案,但是多繼承方案往往違背了類的單一職責原則(即一個類只有一個變化的原因),複用性比較差。Bridge模式是比多繼承方案更好的解決方法。 
4.Bridge模式的應用一般在“兩個非常強的變化維度”,有時候即使有兩個變化的維度,但是某個方向的變化維度並不劇烈——換言之兩個變化不會導致縱橫交錯的結果,並不一定要使用Bridge模式。 

1、橋接模式的優點:

(1)、把抽象介面與其實現解耦。 
(2)、抽象和實現可以獨立擴充套件,不會影響到對方。 
(3)、實現細節對客戶透明,對用於隱藏了具體實現細節。 

2、橋接模式的缺點:

增加了系統的複雜度 

3、橋接模式的使用場景:

(1)、如果一個系統需要在構件的抽象化角色和具體化角色之間新增更多的靈活性,避免在兩個層次之間建立靜態的聯絡。 
(2)、設計要求實現化角色的任何改變不應當影響使用者端,或者實現化角色的改變對使用者端是完全透明的。 
(3)、需要跨越多個平臺的圖形和視窗系統上。 
(4)、 一個類存在兩個獨立變化的維度,且兩個維度都需要進行擴充套件。

下面是針對上面的例子,多繼承介面的一種寫法:

這樣PCT50既需要寫T50的實現,又要寫Platform的實現,它把型號和平臺的變化都引入了PCT50。這樣就把兩個本不該扭在一起的事務扭在了一起,這樣的設計更加糟糕,而且也違背了類的單一職責原則。

Bridge模式的應用一般在“兩個非常強的變化維度”,有時候即使有兩個變化的維度,但是某個方向的變化維度並不劇烈——換言之兩個變化不會導致縱橫交錯的結果,並不一定要使用Bridge模式。

橋模式並不同於介面卡模式,介面卡模式其實是一個事後諸葛亮,當發現以前的東西不適用了才去做一個彌補的措施。橋模式相對來說所做的改變比介面卡模式早,它可以適用於有兩個甚至兩個以上維度的變化。

到此這篇關於.Net結構型設計模式之橋接模式(Bridge)的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com