橋接模式(Bridge)
1.1.1 摘要
在軟件系統(tǒng)中,某些類型由于自身的邏輯,它具有兩個(gè)或兩個(gè)以上的維度變化,那么如何應(yīng)對(duì)這種“多維度的變化”呢?如何利用面向?qū)ο蟮募夹g(shù)來(lái)使得該類型能夠輕松的沿著多個(gè)方向進(jìn)行變化,而又不引入額外的復(fù)雜度呢?這就是即將要介紹的橋接模式(Bridge)。
- 定義
橋接模式(Bridge),將抽象部分與它的實(shí)現(xiàn)部分分離,使它們都可以獨(dú)立地變化。
- 意圖
將抽象與實(shí)現(xiàn)解耦。
- 動(dòng)機(jī)
當(dāng)一種抽象類型可能有多種實(shí)現(xiàn)方式時(shí),一般情況我們可以考慮使用繼承來(lái)解決抽象類型的多種實(shí)現(xiàn),在抽象類型中定義接口,而子類負(fù)責(zé)接口的具體實(shí)現(xiàn)。但這種做法缺乏靈活性,由于抽象類型和子類之間緊緊地綁定在一起,使得這種關(guān)系在運(yùn)行時(shí)不能再修改,這使得它難以修改、擴(kuò)展和重用不利于抽象和實(shí)現(xiàn)解耦,而且這也違背OOP原則:“優(yōu)先使用對(duì)象聚集,而不是繼承”。
- 結(jié)構(gòu)圖
圖1 橋接模式(Bridge)
- 參與者
Abstraction
1. 定義抽象接口。
2. 擁有一個(gè)Implementor類型對(duì)象引用。
- RefinedAbstraction
1. 擴(kuò)展Abstraction中的接口定義。
- Implementor
1. Implementor是具體實(shí)現(xiàn)的接口,Implementor和Abstraction接口并不一定完全一致(注:Proxy和ISubject接口一一對(duì)應(yīng)),實(shí)際上這兩個(gè)接口可以完全不一樣,Implementor提供具體操作方法,而Abstraction提供更高層次的調(diào)用。
- ConcreteImplementor
1. 實(shí)現(xiàn)Implementor接口,給出具體實(shí)現(xiàn)。
1.1.2 正文
首先根據(jù)橋接模式(Bridge)的定義:將抽象部分與它的實(shí)現(xiàn)部分分離,但令我不解的是,怎樣才能將抽象與其實(shí)現(xiàn)的具體方式分離呢?其實(shí)我的迷惑主要是因?yàn)檎`解了實(shí)現(xiàn)的含義。這里實(shí)現(xiàn)指的是抽象類及其派生類用來(lái)實(shí)現(xiàn)自定的對(duì)象(而不是抽象類的派生類,這些派生類被稱為具體類)。不過(guò)這樣還是難以理解,現(xiàn)在讓我們通過(guò)具體的例子說(shuō)明。
我們必需了解橋接模式(Bridge)存在的價(jià)值,然后推遲該模式,OK我們通過(guò)一個(gè)簡(jiǎn)單的例子說(shuō)明為什么需要橋接模式(Bridge)吧!
從一個(gè)繪制形狀的簡(jiǎn)單問(wèn)題開(kāi)始,假設(shè)我們接受一個(gè)任務(wù):編寫(xiě)一個(gè)程序,使用兩個(gè)繪圖程序DrawProgram1和DrawProgram2之一繪制圖形(矩形,圓形等),而且我們被告知,實(shí)例化圖形的時(shí)候,它會(huì)知道應(yīng)該使用繪圖程序DP1還是DP2。
通過(guò)分析我們可以找出抽象類Shape,然后定義Retangle和Circle類繼承抽象類Shape,還有就是繪圖程序DP1和DP2。
圖2繪圖程序類圖
我們現(xiàn)在初步定義了相關(guān)的方法和類,而把Shape,Retangle和Circle都定義為抽象類型方便以后擴(kuò)展。但我們可以發(fā)現(xiàn)圖形類型并沒(méi)有與繪圖程序關(guān)聯(lián)起來(lái),而且前面需求中提到圖形實(shí)例化時(shí)候知道具體調(diào)用哪個(gè)繪圖程序。OK那么現(xiàn)在讓我們把圖形和繪圖程序關(guān)聯(lián)起來(lái)。
圖3繪圖程序類圖
我們定義了Retangle1和Retangle2繼承于抽象類Retangle,添加DrawLine()方法分別調(diào)用DP1和DP2的DrawLine()方法,并且Circle1和Circle2中的實(shí)現(xiàn)基本相同。我們使用了一種直截了當(dāng)?shù)姆椒?,?shí)現(xiàn)了兩種圖形和兩個(gè)繪圖程序的關(guān)聯(lián)。
現(xiàn)在Shape有四個(gè)具體類型(Retangle1,Retangle2,Circle1和Circle2),而且每個(gè)具體類型都和相應(yīng)繪圖程序?qū)τ?,但我們要記住“沒(méi)有不變的需求,世上的軟件都改動(dòng)過(guò)三次以上,唯一一個(gè)只改動(dòng)過(guò)兩次的軟件的擁有者已經(jīng)死了,死在去修改需求的路上”,所以需求總是在不斷的變化之中,如果添加新的繪圖程序DP3,那么我們就要增加兩個(gè)具體類型,而且具體類型中調(diào)用DP3方法跟之前調(diào)用DP1、DP2并沒(méi)有太大的區(qū)別(冗余問(wèn)題),現(xiàn)在有兩種圖形(Retangle和Cirle)和三個(gè)繪圖程序(DP1,DP2和DP3),那么就擁有六種不同Shape(2種圖形 * 3個(gè)繪圖程序),如果我們繼續(xù)擴(kuò)展成三種圖形那么就具有九種不同Shape(3種圖形 * 3個(gè)繪圖程序),這就會(huì)導(dǎo)致“類爆炸”問(wèn)題。
上面的解決方法,因?yàn)槌橄箢愋停⊿hape)和繪圖程序之間是緊耦合,于是存在嚴(yán)重的“類爆炸”問(wèn)題(每種形狀都必須知道自己用哪個(gè)繪圖程序)。我們需要一種方式將抽象上的變化和實(shí)現(xiàn)變化進(jìn)行解耦。
將抽象與實(shí)現(xiàn)解耦這不就是橋接模式(Bridge)的意圖嗎?在介紹橋接模式(Bridge)之前,我們總結(jié)一下前面方式中的問(wèn)題。
- 存在冗余
- 低內(nèi)聚
- 緊耦合
由于前面的例子按照不同圖形來(lái)進(jìn)行繼承的分類,如果我們按照不同繪圖程序分類結(jié)果又如何呢?OK,那么讓我們畫(huà)出按照不同繪圖程序分類類圖。
圖4繪圖程序類圖
現(xiàn)在我們繼續(xù)使用四個(gè)類表示現(xiàn)有的圖形的組合,但這里我們按照不同繪圖程序派生不同圖形,所以我們消除了圖形類(Retangle1,Retangle2, Circle1和Circle2)和繪圖程序的之間的緊耦合,從而消除了它們之間的冗余?,F(xiàn)在把耦合轉(zhuǎn)移到更高的繼承層次,但問(wèn)題有出現(xiàn)了當(dāng)有新的繪圖程序加入時(shí),我們的確可以輕松地進(jìn)行擴(kuò)展,只要增加ShapeDP3類繼承抽象類Shape就OK了,但是要實(shí)現(xiàn)一套一模一樣的Retangle3和Circle3了。
盡管這種方式對(duì)前面的方式有所改進(jìn),但冗余和耦合問(wèn)題依然存在。
在我們每次使用設(shè)計(jì)模式時(shí),我們應(yīng)該根據(jù)設(shè)計(jì)原則去設(shè)計(jì),而不是直接使用已有設(shè)計(jì)模式去套用,我們要明白的一點(diǎn)是設(shè)計(jì)模式是根據(jù)一定的設(shè)計(jì)原則而產(chǎn)生的。
這次我們要遵循兩個(gè)基本原則:
- 找出變化封裝之
- 優(yōu)先使用對(duì)象聚集,而不是繼承
首先我們可以很快的找出需求中變化:圖形和繪圖程序,然后我們使用抽象來(lái)封裝變化就OK了。
圖5封裝繪圖程序中變化
現(xiàn)在我們已經(jīng)找出了變化圖形和繪圖程序,注意這里的OperationalDP1和OperationalDP2作為調(diào)用繪圖程序(DP1和DP2)的接口,因?yàn)镈P1和DP2是兩個(gè)已經(jīng)存在的程序所為我們無(wú)法直接抽象出DP1和DP2的高層接口,通過(guò)一種間接方式抽象出高層接口,使用抽象類把變化封裝在它的“后面”,接著我們就是要在抽象類Shape和DrawingProgramming直接建立依賴關(guān)系了(優(yōu)先使用對(duì)象聚集,而不是繼承),所以可以通過(guò)在其中一個(gè)抽象類中保持對(duì)方的引用就OK了,但究竟是哪個(gè)類依賴于哪個(gè)類呢?
這里存在兩種情形:
一、DrawProgramming類保存Shape對(duì)象引用
二、Shape類保存DrawProgramming對(duì)象引用
首先考慮第一種情形,如果在DrawProgramming保存Shape對(duì)象引用,我們通過(guò)調(diào)用Draw()方法繪制圖形,但它們必需對(duì)Shape類中的圖形有所了解(Draw()方法將具體圖形封裝了),當(dāng)我們使用OperationalDP1中的DrawCircle()方法時(shí),就要知道Shape中的Circle類型,這違反了對(duì)象應(yīng)該只對(duì)自己負(fù)責(zé)。
圖6 情形一
第二種情形,如果Shape對(duì)象使用DrawProgramming對(duì)象繪制圖形時(shí),圖形無(wú)需知道具體繪圖程序。當(dāng)使用Circle中的Draw()方法我們只需要調(diào)用DrawProgramming中的方法DrawCircle(),因此可以讓Shape保存DrawProgramming的對(duì)象引用。
圖6 情形二
通過(guò)分析我們可以確定采用情形二實(shí)現(xiàn)起來(lái)相對(duì)簡(jiǎn)單,接著在抽象類Shape增加DrawProgramming對(duì)象引用,從而在Shape和DrawProgramming之間建立了一種聚集關(guān)系(has – a關(guān)系)。
現(xiàn)在讓我們回憶一下橋接模式(Bridge)的定義:將抽象部分與它的實(shí)現(xiàn)部分分離,使它們都可以獨(dú)立地變化。Shape類及其實(shí)現(xiàn)就是抽象部分,而DrawingProgramming及其實(shí)現(xiàn)是具體部分,通過(guò)聚集使得它們分離開(kāi)來(lái),從而應(yīng)對(duì)變化和擴(kuò)展更加靈活。
圖7 橋接模式實(shí)現(xiàn)繪圖程序
我們已經(jīng)完成了繪圖程序的類設(shè)計(jì),而且通過(guò)該程序類設(shè)計(jì)我們了解到了橋接模式(Bridge)的作用,現(xiàn)在讓我們通過(guò)具體代碼完成繪圖程序。
/// <summary> /// As Abstraction. /// </summary> public abstract class Shape { /// <summary> /// Has a reference from DrawingProgramming. /// </summary> private DrawingProgramming _dp; public Shape() { } public Shape(DrawingProgramming dp) { this.Dp = dp; } public abstract void Draw(); public DrawingProgramming Dp { get { return _dp; } set { _dp = value; } } } /// <summary> /// As Refined Abstraction. /// </summary> public class Retangle : Shape { private int _width = 0; private int _height = 0; public Retangle(DrawingProgramming dp, int width, int height) : base(dp) { this.Width = width; this.Height = height; } public override void Draw() { this.DrawRetangle(Width, Height); } public void DrawRetangle(int width, int height) { this.Dp.DrawRetangle(width, height); } public int Width { get { return _width; } set { _width = value; } } public int Height { get { return _height; } set { _height = value; } } } /// <summary> /// As Refined Abstraction. /// </summary> public class Triangle : Shape { private int _rows = 0; public Triangle(DrawingProgramming dp, int rows) : base(dp) { this.Rows = rows; } public override void Draw() { this.DrawTriangle(this.Rows); } public void DrawTriangle(int rows) { this.Dp.DrawTriangle(rows); } public int Rows { get { return _rows; } set { _rows = value; } } } /// <summary> /// As Implementor. /// </summary> public abstract class DrawingProgramming { /// <summary> /// Abstract draw method. /// </summary> /// <param name="width"></param> /// <param name="height"></param> public abstract void DrawRetangle(int width, int height); public abstract void DrawTriangle(int Rows); } /// <summary> /// As concrete Implementor /// </summary> public class OperationalDP1 : DrawingProgramming { public OperationalDP1() { } public DP1 DP1 { get { throw new System.NotImplementedException(); } set { } } public override void DrawRetangle(int width, int height) { DP1.DrawRetangle(width, height); } public override void DrawTriangle(int Rows) { DP1.DrawTriangle(Rows); } } /// <summary> /// As concrete Implementor /// </summary> public class OperationalDP2 : DrawingProgramming { public OperationalDP2() { } public DP2 DP2 { get { throw new System.NotImplementedException(); } set { } } public override void DrawRetangle(int width, int height) { DP2.DrawRetangle(width, height); } public override void DrawTriangle(int Rows) { DP2.DrawTriangle(Rows); } } /// <summary> /// existed drawing programming. /// </summary> public class DP1 { /// <summary> /// Concrete draw method. /// </summary> /// <param name="width"></param> /// <param name="height"></param> public static void DrawRetangle(int width, int height) { for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { Console.Write("■"); } Console.WriteLine(); } } public static void DrawTriangle(int Rows) { for (int i = 0; i < Rows; i++) { for (int j = 0; j < i + 1; j++) { Console.Write("■"); } Console.WriteLine(); } } } /// <summary> /// existed drawing programming. /// </summary> public class DP2 { /// <summary> /// Concrete draw method. /// </summary> /// <param name="width"></param> /// <param name="height"></param> public static void DrawRetangle(int width, int height) { for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { Console.Write("★"); } Console.WriteLine(); } } public static void DrawTriangle(int Rows) { for (int i = 0; i < Rows; i++) { for (int j = 0; j < i + 1; j++) { Console.Write("★"); } Console.WriteLine(); } } }
通過(guò)上面的例子我們對(duì)橋接模式(Bridge)有了初步的了解,假設(shè)我們的繪圖程序DP1擁有特有擦除方法Wipe(),從而需要在OperationalDP1類中增加相應(yīng)的方法,但沒(méi)有必要修改DrawingProgramming類,現(xiàn)在問(wèn)題出現(xiàn)了我們要在抽象類Shpe中增加Wipe()方法,但這種修改是我們?cè)敢饪吹降?。這時(shí)我們可以考慮以下兩種方法解決問(wèn)題。
方法一:在基類中添加虛的方法,然后需要的子類重寫(xiě)基類的虛方法。
方法二:使用.NET Framework中的擴(kuò)展方法,對(duì)基類方法進(jìn)行擴(kuò)展。
1.1.3 總結(jié)
橋接模式(Bridge)優(yōu)點(diǎn):
將實(shí)現(xiàn)予以解耦,讓它和界面之間不再永久綁定。
抽象和實(shí)現(xiàn)可以獨(dú)立擴(kuò)展,不會(huì)影響到對(duì)方。
對(duì)于具體實(shí)現(xiàn)的修改,不會(huì)影響到客戶端。
橋接模式(Bridge)缺點(diǎn):
增加了設(shè)計(jì)復(fù)雜度。
抽象類的修改影響到子類。
橋接模式(Bridge)用途:
適用在需要跨多平臺(tái)的圖形和窗口系統(tǒng)。
當(dāng)需要用不同的方式改變接口和實(shí)現(xiàn)時(shí)。
通過(guò)上述的介紹,我們了解為什么需要橋接模式(Bridge)和如何使用橋接模式(Bridge),由于對(duì)象的多維度的變化,使得難以決定變化時(shí),我們可以把對(duì)象和變化抽象出來(lái)。
如果我們的對(duì)象依賴于抽象,對(duì)于具體的實(shí)現(xiàn)并不關(guān)心,我們可以通過(guò)對(duì)象組合,組合出我們想要的對(duì)象。橋接模式符合OCP(對(duì)于擴(kuò)展開(kāi)發(fā),對(duì)于修改關(guān)閉)設(shè)計(jì)模式的原則。
|
|
關(guān)于作者:[作者]:
JK_Rush從事.NET開(kāi)發(fā)和熱衷于開(kāi)源高性能系統(tǒng)設(shè)計(jì),通過(guò)博文交流和分享經(jīng)驗(yàn),歡迎轉(zhuǎn)載,請(qǐng)保留原文地址,謝謝。 |

1.1.1 摘要 在軟件系統(tǒng)中,某些類型由于自身的邏輯,它具有兩個(gè)或兩個(gè)以上的維度變化,那么如何應(yīng)對(duì)這種“多維度的變化”呢?如何利用面向?qū)ο蟮募夹g(shù)來(lái)使得該類型能夠輕松的沿著多個(gè)方向進(jìn)行變化,而又不引...
![clip_image001[4] clip_image001[4]](https://images.cnblogs.com/cnblogs_com/rush/201106/201106292037599729.gif)








浙公網(wǎng)安備 33010602011771號(hào)