一、引言
這裡以電視遙控器的一個例子來引出橋接模式解決的問題,首先,我們每個牌子的電視機都有一個遙控器,此時我們能想到的一個設計是——把遙控器做為一個抽象類,抽象類中提供遙控器的所有實現,其他具體電視品牌的遙控器都繼承這個抽象類,具體設計類圖如下:
這樣的實現使得每部不同型號的電視都有自己遙控器實現,這樣的設計對於電視機的改變可以很好地應對,只需要新增一個派生類就搞定了,但隨著時間的推移,使用者需要改變遙控器的功能,如:使用者可能後面需要對遙控器新增返回上一個臺等功能時,此時上面的設計就需要修改抽象類RemoteControl的提供的介面了,此時可能只需要向抽象類中新增一個方法就可以解決了,但是這樣帶來的問題是我們改變了抽象的實現,如果使用者需要同時改變電視機品型號和遙控器功能時,上面的設計就會導致相當大的修改,顯然這樣的設計並不是好的設計。然而使用橋接模式可以很好地解決這個問題,下面讓我具體看看橋接模式是如何實現的。
二、橋接模式的詳細介紹
2.1 定義
橋接模式即將抽象部分與實現部分脫耦,使它們可以獨立變化。對於上面的問題中,抽象化也就是RemoteControl類,實現部分也就是On()、Off()、NextChannel()等這樣的方法(即遙控器的實現),上面的設計中,抽象化和實現部分在一起,橋接模式的目的就是使兩者分離,根據物件導向的封裝變化的原則,我們可以把實現部分的變化(也就是遙控器功能的變化)封裝到另外一個類中,這樣的一個思路也就是橋接模式的實現,大家可以對照橋接模式的實現程式碼來解決我們的分析思路。
2.2 橋接模式實現
上面定義部分已經給出了我們橋接模式的目的以及實現思路了,下面讓我們具體看看橋接模式是如何解決引言部分設計的不足。
抽象化部分的程式碼:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
/// <summary> /// 抽象概念中的遙控器,扮演抽象化角色 /// </summary> public class RemoteControl { // 欄位 private TV implementor; // 屬性 public TV Implementor { get { return implementor; } set { implementor = value; } } /// <summary> /// 開電視機,這裡抽象類中不再提供實現了,而是呼叫實現類中的實現 /// </summary> public virtual void On() { implementor.On(); } /// <summary> /// 關電視機 /// </summary> public virtual void Off() { implementor.Off(); } /// <summary> /// 換頻道 /// </summary> public virtual void SetChannel() { implementor.tuneChannel(); } } /// <summary> /// 具體遙控器 /// </summary> public class ConcreteRemote : RemoteControl { public override void SetChannel() { Console.WriteLine("---------------------"); base.SetChannel(); Console.WriteLine("---------------------"); } } |
遙控器的實現方法部分程式碼,即實現化部分程式碼,此時我們用另外一個抽象類TV封裝了遙控器功能的變化,具體實現交給具體型號電視機去完成:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
/// <summary> /// 電視機,提供抽象方法 /// </summary> public abstract class TV { public abstract void On(); public abstract void Off(); public abstract void tuneChannel(); } /// <summary> /// 長虹牌電視機,重寫基類的抽象方法 /// 提供具體的實現 /// </summary> public class ChangHong : TV { public override void On() { Console.WriteLine("長虹牌電視機已經開啟了"); } public override void Off() { Console.WriteLine("長虹牌電視機已經關掉了"); } public override void tuneChannel() { Console.WriteLine("長虹牌電視機換頻道"); } } /// <summary> /// 三星牌電視機,重寫基類的抽象方法 /// </summary> public class Samsung : TV { public override void On() { Console.WriteLine("三星牌電視機已經開啟了"); } public override void Off() { Console.WriteLine("三星牌電視機已經關掉了"); } public override void tuneChannel() { Console.WriteLine("三星牌電視機換頻道"); } } |
採用橋接模式的客戶端呼叫程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/// <summary> /// 以電視機遙控器的例子來演示橋接模式 /// </summary> class Client { static void Main(string[] args) { // 建立一個遙控器 RemoteControl remoteControl = new ConcreteRemote(); // 長虹電視機 remoteControl.Implementor = new ChangHong(); remoteControl.On(); remoteControl.SetChannel(); remoteControl.Off(); Console.WriteLine(); // 三星牌電視機 remoteControl.Implementor = new Samsung(); remoteControl.On(); remoteControl.SetChannel(); remoteControl.Off(); Console.Read(); } } |
上面橋接模式的實現中,遙控器的功能實現方法不在遙控器抽象類中去實現了,而是把實現部分用來另一個電視機類去封裝它,然而遙控器中只包含電視機類的一個引用,同時這樣的設計也非常符合現實生活中的情況(我認為的現實生活中遙控器的實現——遙控器中並不包含換臺,開啟電視機這樣的功能的實現,遙控器只是包含了電視機上這些功能的引用,然後紅外線去找到電視機上對應功能的的實現)。通過橋接模式,我們把抽象化和實現化部分分離開了,這樣就可以很好應對這兩方面的變化了。
2.3 橋接模式的類圖
看完橋接模式的實現後,為了幫助大家理清對橋接模式中類之間關係,這裡給出橋接模式的類圖結構:
三、橋接模式的優缺點
介紹完橋接模式,讓我們看看橋接模式具體哪些優缺點。
優點:
把抽象介面與其實現解耦。
抽象和實現可以獨立擴充套件,不會影響到對方。
實現細節對客戶透明,對用於隱藏了具體實現細節。
缺點: 增加了系統的複雜度
四、使用場景
我們再來看看橋接模式的使用場景,在以下情況下應當使用橋接模式:
- 如果一個系統需要在構件的抽象化角色和具體化角色之間新增更多的靈活性,避免在兩個層次之間建立靜態的聯絡。
- 設計要求實現化角色的任何改變不應當影響客戶端,或者實現化角色的改變對客戶端是完全透明的。
- 需要跨越多個平臺的圖形和視窗系統上。
- 一個類存在兩個獨立變化的維度,且兩個維度都需要進行擴充套件。
五、一個實際應用橋接模式的例子
橋接模式也經常用於具體的系統開發中,對於三層架構中就應用了橋接模式,三層架構中的業務邏輯層BLL中通過橋接模式與資料操作層解耦(DAL),其實現方式就是在BLL層中引用了DAL層中一個引用。這樣資料操作的實現可以在不改變客戶端程式碼的情況下動態進行更換,下面看一個簡單的示例程式碼:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
// 客戶端呼叫 // 類似Web應用程式 class Client { static void Main(string[] args) { BusinessObject customers = new CustomersBusinessObject("ShangHai"); customers.Dataacces = new CustomersDataAccess(); customers.Add("小六"); Console.WriteLine("增加了一位成員的結果:"); customers.ShowAll(); customers.Delete("王五"); Console.WriteLine("刪除了一位成員的結果:"); customers.ShowAll(); Console.WriteLine("更新了一位成員的結果:"); customers.Update("Learning_Hard"); customers.ShowAll(); Console.Read(); } } // BLL 層 public class BusinessObject { // 欄位 private DataAccess dataacess; private string city; public BusinessObject(string city) { this.city = city; } // 屬性 public DataAccess Dataacces { get { return dataacess; } set { dataacess = value; } } // 方法 public virtual void Add(string name) { Dataacces.AddRecord(name); } public virtual void Delete(string name) { Dataacces.DeleteRecord(name); } public virtual void Update(string name) { Dataacces.UpdateRecord(name); } public virtual string Get(int index) { return Dataacces.GetRecord(index); } public virtual void ShowAll() { Console.WriteLine(); Console.WriteLine("{0}的顧客有:", city); Dataacces.ShowAllRecords(); } } public class CustomersBusinessObject : BusinessObject { public CustomersBusinessObject(string city) : base(city) { } // 重寫方法 public override void ShowAll() { Console.WriteLine("------------------------"); base.ShowAll(); Console.WriteLine("------------------------"); } } /// <summary> /// 相當於三層架構中資料訪問層(DAL) /// </summary> public abstract class DataAccess { // 對記錄的增刪改查操作 public abstract void AddRecord(string name); public abstract void DeleteRecord(string name); public abstract void UpdateRecord(string name); public abstract string GetRecord(int index); public abstract void ShowAllRecords(); } public class CustomersDataAccess:DataAccess { // 欄位 private List<string> customers =new List<string>(); public CustomersDataAccess() { // 實際業務中從資料庫中讀取資料再填充列表 customers.Add("Learning Hard"); customers.Add("張三"); customers.Add("李四"); customers.Add("王五"); } // 重寫方法 public override void AddRecord(string name) { customers.Add(name); } public override void DeleteRecord(string name) { customers.Remove(name); } public override void UpdateRecord(string updatename) { customers[0] = updatename; } public override string GetRecord(int index) { return customers[index]; } public override void ShowAllRecords() { foreach (string name in customers) { Console.WriteLine(" " + name); } } } |
六、總結
到這裡,橋接模式的介紹就介紹,橋接模式實現了抽象化與實現化的解耦,使它們相互獨立互不影響到對方。