.Net結(jié)構(gòu)型設(shè)計(jì)模式之橋接模式(Bridge)
一、動(dòng)機(jī)(Motivation)
在很多游戲場(chǎng)景中,會(huì)有這樣的情況:【裝備】本身會(huì)有的自己固有的邏輯,比如槍支,會(huì)有型號(hào)的問(wèn)題,同時(shí)現(xiàn)在很多的游戲又在不同的介質(zhì)平臺(tái)上運(yùn)行和使用,這樣就使得游戲的【裝備】具有了兩個(gè)變化的維度——一個(gè)變化的維度為“平臺(tái)的變化”,另一個(gè)變化的維度為“型號(hào)的變化”。如果我們要寫(xiě)代碼實(shí)現(xiàn)這款游戲,難道我們針對(duì)每種平臺(tái)都實(shí)現(xiàn)一套獨(dú)立的【裝備】嗎?復(fù)用在哪里?如何應(yīng)對(duì)這種“多維度的變化”?如何利用面向?qū)ο蠹夹g(shù)來(lái)使得【裝備】可以輕松地沿著“平臺(tái)”和“型號(hào)”兩個(gè)方向變化,而不引入額外的復(fù)雜度?
二、意圖(Intent)
將抽象部分與實(shí)現(xiàn)部分分離,使它們都可以獨(dú)立地變化。
橋模式不能只是認(rèn)為是抽象和實(shí)現(xiàn)的分離,它其實(shí)并不僅限于此。其實(shí)兩個(gè)都是抽象的部分,更確切的理解,應(yīng)該是將一個(gè)事物中多個(gè)維度的變化分離。
三、結(jié)構(gòu)(Structure)
其中imp的地方就是一個(gè)組合。Abstraction就是我們例子中的Tank,它的子類(lèi)RefinedAbstraction就是T50等型號(hào)。Implementor是TankPlatformImplementation類(lèi),ConcreteImplementorA和ConcreteImplementorB分別是PCTankImplementation和MobileTankImplementation。
整個(gè)設(shè)計(jì)模式的關(guān)鍵就是組合的使用。
四、模式的組成
橋接模式的結(jié)構(gòu)包括Abstraction、RefinedAbstraction、Implementor、ConcreteImplementorA和ConcreteImplementorB五個(gè)部分,其中:
(1)、抽象化角色(Abstraction):抽象化給出的定義,并保存一個(gè)對(duì)實(shí)現(xiàn)化對(duì)象(Implementor)的引用。
(2)、修正抽象化角色(Refined Abstraction):擴(kuò)展抽象化角色,改變和修正父類(lèi)對(duì)抽象化的定義。
(3)、實(shí)現(xiàn)化角色(Implementor):這個(gè)角色給出實(shí)現(xiàn)化角色的接口,但不給出具體的實(shí)現(xiàn)。必須指出的是,這個(gè)接口不一定和抽象化角色的接口定義相同,實(shí)際上,這兩個(gè)接口可以非常不一樣。實(shí)現(xiàn)化角色應(yīng)當(dāng)只給出底層操作,而抽象化角色應(yīng)當(dāng)只給出基于底層操作的更高一層的操作。
(4)、具體實(shí)現(xiàn)化角色(Concrete Implementor):這個(gè)角色給出實(shí)現(xiàn)化角色接口的具體實(shí)現(xiàn)。
在橋接模式中,兩個(gè)類(lèi)Abstraction和Implementor分別定義了抽象與行為類(lèi)型的接口,通過(guò)調(diào)用兩接口的子類(lèi)實(shí)現(xiàn)抽象與行為的動(dòng)態(tài)組合。
五、橋接模式的具體代碼實(shí)現(xiàn)
假如我們需要開(kāi)發(fā)一個(gè)同時(shí)支持PC和手機(jī)端的坦克游戲,游戲在PC和手機(jī)上功能都一樣,都有同樣的類(lèi)型,面臨同樣的功能需求變化,比如坦克可能有很多種不同的型號(hào):T50,T75,T90……
對(duì)于其中的坦克設(shè)計(jì),我們可能很容易設(shè)計(jì)出來(lái)一個(gè)Tank的抽象基類(lèi),然后各種不同型號(hào)的Tank繼承自該類(lèi);
這一步實(shí)現(xiàn)一點(diǎn)問(wèn)題也沒(méi)有,也符合開(kāi)閉原則,繼續(xù)往下看。
另外的變化原因
但是PC和手機(jī)上的圖形繪制、聲效、操作等實(shí)現(xiàn)完全不同……因此對(duì)于各種型號(hào)的坦克,都要提供各種不同平臺(tái)上的坦克實(shí)現(xiàn):
我們一般會(huì)設(shè)計(jì)成這樣,但是這樣看很怪,這樣的設(shè)計(jì)會(huì)帶來(lái)很多問(wèn)題:有很多重復(fù)代碼,類(lèi)的結(jié)構(gòu)過(guò)于復(fù)雜,難以維護(hù),最致命的是引入任何新平臺(tái),比如在TV上的Tank游戲,都會(huì)讓整個(gè)類(lèi)層級(jí)結(jié)構(gòu)復(fù)雜化。我們做軟件,修改的時(shí)候,修改的越少越好,說(shuō)明隔離的比較好。
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坦克繪制成功!"); } /// /// 坦克移動(dòng) /// /// x坐標(biāo) /// y坐標(biāo) public override void MoveTankTo(int x, int y) { Console.WriteLine(_tankModel + "PC坦克已經(jīng)移動(dòng)到了坐標(biāo)(" + x + "," + y + ")處"); } /// /// 攻擊 /// public override void Attack() { Console.WriteLine(_tankModel + "PC坦克開(kāi)始攻擊"); } } /// /// T50型號(hào)坦克 /// public class T50 : TankModel { public T50(TankPlatformImplementation tankImp) : base(tankImp) { } public override void Run() { _tankImp.DrawTank(); _tankImp.MoveTankTo(100, 100); _tankImp.Attack(); } } /// /// 客戶(hù)端調(diào)用 /// public class App { void Main(string[] agrs) { T50 t = new T50(new PCTankImplatation("T50")); t.Run(); } }
使用了橋接模式后,當(dāng)需求發(fā)生變化后就很容易來(lái)應(yīng)對(duì)了,假如現(xiàn)在又多了一種T60型號(hào)的坦克,并且添加了一個(gè)手機(jī)平臺(tái)。只需要添加T60型號(hào)的具體類(lèi)和手機(jī)平臺(tái)具體類(lèi)即可,如下:
/// /// 手機(jī)坦克 /// public class MobileTankImplatation : TankPlatformImplementation { string _tankModel; public MobileTankImplatation(string tankModel) { _tankModel = tankModel; } /// /// 繪制坦克 /// public override void DrawTank() { Console.WriteLine(_tankModel+"Mobile坦克繪制成功!"); } /// /// 坦克移動(dòng) /// /// x坐標(biāo) /// y坐標(biāo) public override void MoveTankTo(int x, int y) { Console.WriteLine(_tankModel+"Mobile坦克已經(jīng)移動(dòng)到了坐標(biāo)(" + x + "," + y + ")處"); } /// /// 攻擊 /// public override void Attack() { Console.WriteLine(_tankModel+"Mobile坦克開(kāi)始攻擊"); } } /// /// T60型號(hào)坦克 /// public class T60 : TankModel { public T60(TankPlatformImplementation tankImp) : base(tankImp) { } public override void Run() { _tankImp.DrawTank(); _tankImp.MoveTankTo(400, 100); _tankImp.Attack(); } }
添加這兩個(gè)類(lèi)后現(xiàn)在我們有T50型號(hào)、 T60型號(hào) 、PC平臺(tái)、手機(jī)平臺(tái),雖然只添加了兩個(gè)類(lèi),但現(xiàn)在有了四種組合,看客戶(hù)端代碼的調(diào)用:
/// /// 客戶(hù)端調(diào)用 /// 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(); } }
六、橋接模式的實(shí)現(xiàn)要點(diǎn):
1.Bridge模式使用“對(duì)象間的組合關(guān)系”解耦了抽象和實(shí)現(xiàn)之間固有的綁定關(guān)系,使得抽象和實(shí)現(xiàn)可以沿著各自的維度來(lái)變化。
2.所謂抽象和實(shí)現(xiàn)沿著各自維度的變化,即“子類(lèi)化”它們,得到各個(gè)子類(lèi)之后,便可以任意組合它們,從而獲得不同平臺(tái)上的不同型號(hào)。
3.Bridge模式有時(shí)候類(lèi)似于多繼承方案,但是多繼承方案往往違背了類(lèi)的單一職責(zé)原則(即一個(gè)類(lèi)只有一個(gè)變化的原因),復(fù)用性比較差。Bridge模式是比多繼承方案更好的解決方法。
4.Bridge模式的應(yīng)用一般在“兩個(gè)非常強(qiáng)的變化維度”,有時(shí)候即使有兩個(gè)變化的維度,但是某個(gè)方向的變化維度并不劇烈——換言之兩個(gè)變化不會(huì)導(dǎo)致縱橫交錯(cuò)的結(jié)果,并不一定要使用Bridge模式。
1、橋接模式的優(yōu)點(diǎn):
(1)、把抽象接口與其實(shí)現(xiàn)解耦。
(2)、抽象和實(shí)現(xiàn)可以獨(dú)立擴(kuò)展,不會(huì)影響到對(duì)方。
(3)、實(shí)現(xiàn)細(xì)節(jié)對(duì)客戶(hù)透明,對(duì)用于隱藏了具體實(shí)現(xiàn)細(xì)節(jié)。
2、橋接模式的缺點(diǎn):
增加了系統(tǒng)的復(fù)雜度
3、橋接模式的使用場(chǎng)景:
(1)、如果一個(gè)系統(tǒng)需要在構(gòu)件的抽象化角色和具體化角色之間添加更多的靈活性,避免在兩個(gè)層次之間建立靜態(tài)的聯(lián)系。
(2)、設(shè)計(jì)要求實(shí)現(xiàn)化角色的任何改變不應(yīng)當(dāng)影響客戶(hù)端,或者實(shí)現(xiàn)化角色的改變對(duì)客戶(hù)端是完全透明的。
(3)、需要跨越多個(gè)平臺(tái)的圖形和窗口系統(tǒng)上。
(4)、 一個(gè)類(lèi)存在兩個(gè)獨(dú)立變化的維度,且兩個(gè)維度都需要進(jìn)行擴(kuò)展。
下面是針對(duì)上面的例子,多繼承接口的一種寫(xiě)法:
這樣PCT50既需要寫(xiě)T50的實(shí)現(xiàn),又要寫(xiě)Platform的實(shí)現(xiàn),它把型號(hào)和平臺(tái)的變化都引入了PCT50。這樣就把兩個(gè)本不該扭在一起的事務(wù)扭在了一起,這樣的設(shè)計(jì)更加糟糕,而且也違背了類(lèi)的單一職責(zé)原則。
Bridge模式的應(yīng)用一般在“兩個(gè)非常強(qiáng)的變化維度”,有時(shí)候即使有兩個(gè)變化的維度,但是某個(gè)方向的變化維度并不劇烈——換言之兩個(gè)變化不會(huì)導(dǎo)致縱橫交錯(cuò)的結(jié)果,并不一定要使用Bridge模式。
橋模式并不同于適配器模式,適配器模式其實(shí)是一個(gè)事后諸葛亮,當(dāng)發(fā)現(xiàn)以前的東西不適用了才去做一個(gè)彌補(bǔ)的措施。橋模式相對(duì)來(lái)說(shuō)所做的改變比適配器模式早,它可以適用于有兩個(gè)甚至兩個(gè)以上維度的變化。
到此這篇關(guān)于.Net結(jié)構(gòu)型設(shè)計(jì)模式之橋接模式(Bridge)的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
ASP.NET web.config 配置節(jié)點(diǎn)詳解
這篇文章主要介紹了ASP.NET web.config 節(jié)點(diǎn)的配置,講解的非常詳細(xì),需要的朋友可以參考下。2016-06-06Asp.net中Request.Url的各個(gè)屬性對(duì)應(yīng)的意義介紹
網(wǎng)絡(luò)上關(guān)于Request.Url的說(shuō)明已經(jīng)很多也很豐富了,但是自己還是實(shí)踐了一下,看看最終的結(jié)果與網(wǎng)絡(luò)上的是否一致2012-05-05ASP.NET Core基礎(chǔ)之啟動(dòng)設(shè)置
這篇文章介紹了ASP.NET Core基礎(chǔ)之啟動(dòng)設(shè)置,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-02-02.NET設(shè)計(jì)模式之UML類(lèi)圖介紹
這篇文章介紹了.NET設(shè)計(jì)模式之UML類(lèi)圖,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05ASP.NET 5已終結(jié),迎來(lái)ASP.NET Core 1.0和.NET Core 1.0
命名是非常困難的事情,微軟這次為了和ASP.NET4.6做區(qū)分,采用了全新的命名方式ASP.NET Core 1.0,它是一個(gè)全新的框架。2016-03-03