前言:這篇打算從設計模式的角度去解析下委託的使用。我們知道使用委託可以實現物件行為(方法)的動態繫結,從而提高設計的靈活性。上次說過,方法可以理解為委託的例項,站在方法的層面,委託例項的一個非常有用的特性是它既不知道,也不關心其封裝方法所屬類的詳細資訊,對它來說最重要的是這些方法與該委託的引數和返回值的相容性。即只要方法的返回型別和參數列是相同的,則方法與委託型別相容,方法的名稱及方法所屬類等資訊委託是不關心的。有一定程式設計經驗的大俠們肯定都接觸過設計模式,其實設計模式大多數都是物件導向多型特性的體現,通過重寫子類方法去展現不同的設計需求,這樣看,既然是方法重寫,那麼方法的引數型別和返回值型別肯定是一致的,這是不是和委託的例項十分相似,這樣說來,我們通過多型去實現的設計模式是否可以用委託的形式去代替。博主覺得,為了更好的理解委託,可以從這方面著手試試。。。
此篇簡單抽取了幾個設計模式分別按照多型和委託的方式去實現,當然這裡的重點並不是講設計模式,而是為了使讀者更好地理解委託。所以設計模式的很多細節,本篇可能會略過。
一、簡單工廠模式:本篇就藉助計算器的例子加以說明。
1、多型實現簡單工廠模式。
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 |
class Program2 { static void Main(string[] args) { //1.使用多型實現簡單工廠模式 int x = 8, y = 2; var iRes1 = GetObject("+").Compute(x, y); var iRes2 = GetObject("-").Compute(x, y); var iRes3 = GetObject("*").Compute(x, y); var iRes4 = GetObject("/").Compute(x, y); Console.WriteLine(iRes1); Console.WriteLine(iRes2); Console.WriteLine(iRes3); Console.WriteLine(iRes4); Console.ReadKey(); } static Calculator GetObject(string type) { Calculator oRes = null; switch (type) { case "+": oRes = new Add(); break; case "-": oRes = new Subtract(); break; case "*": oRes = new Multiply(); break; case "/": oRes = new Divide(); break; } return oRes; } } public class Calculator { public virtual int Compute(int x, int y) { return 0; } } public class Add : Calculator { public override int Compute(int x, int y) { return x + y; } } public class Subtract : Calculator { public override int Compute(int x, int y) { return x - y; } } public class Multiply : Calculator { public override int Compute(int x, int y) { return x * y; } } public class Divide : Calculator { public override int Compute(int x, int y) { if (y == 0) { return 0; } return x / y; } } |
程式碼應該很容易看懂,直接通過方法的重寫去實現,在此就不過多講解。
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 53 54 55 56 57 |
class Program2 { static void Main(string[] args) { #region 2.委託實現簡單工廠模式 int x = 8, y = 2; var oCalculator = new Calculator(); var iRes1 = oCalculator.Compute(x, y, oCalculator.Add);//將方法作為引數傳下去 var iRes2 = oCalculator.Compute(x, y, oCalculator.Subtract); var iRes3 = oCalculator.Compute(x, y, oCalculator.Multiply); var iRes4 = oCalculator.Compute(x, y, oCalculator.Divide); Console.WriteLine(iRes1); Console.WriteLine(iRes2); Console.WriteLine(iRes3); Console.WriteLine(iRes4); #endregion Console.ReadKey(); } } public delegate int DelegateCalculator(int x, int y); public class Calculator { //將方法的例項傳遞進來,在Compute方法裡面執行 public int Compute(int x, int y, DelegateCalculator calculator) { return calculator(x, y); } public int Add(int x, int y) { return x + y; } public int Subtract(int x, int y) { return x - y; } public int Multiply(int x, int y) { return x * y; } public int Divide(int x, int y) { if (y == 0) { return 0; } return x / y; } } |
這裡需要定義四個實現方法Add、Subtract、Multiply、Divide,而不用在意這四個方法在哪個類下面,只要這四個方法的的引數和返回值和委託的定義保持一致即可。這也驗證了上面說的 “站在方法的層面,委託例項的一個非常有用的特性是它既不知道,也不關心其封裝方法所屬類的詳細資訊,對它來說最重要的是這些方法與該委託的引數和返回值的相容性” 。兩種方式得到的結果是相同的:
二、觀察者模式:觀察者模式最典型的場景就是訂閱者和訂閱號的場景
1、純多型方式實現觀察者模式:這種程式碼園子裡面非常多。
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 |
class Program3 { static void Main(string[] args) { // 具體主題角色通常用具體自來來實現 ConcreteSubject subject = new ConcreteSubject(); subject.Attach(new ConcreteObserver(subject, "Observer A")); subject.Attach(new ConcreteObserver(subject, "Observer B")); subject.Attach(new ConcreteObserver(subject, "Observer C")); subject.SubjectState = "Ready"; subject.Notify(); Console.Read(); } } //抽象主題類 public abstract class Subject { private IList<Observer> observers = new List<Observer>(); /// <summary> /// 增加觀察者 /// </summary> /// <param name="observer"></param> public void Attach(Observer observer) { observers.Add(observer); } /// <summary> /// 移除觀察者 /// </summary> /// <param name="observer"></param> public void Detach(Observer observer) { observers.Remove(observer); } /// <summary> /// 向觀察者(們)發出通知 /// </summary> public void Notify() { foreach (Observer o in observers) { o.Update(); } } } //具體主題類 public class ConcreteSubject : Subject { private string subjectState; /// <summary> /// 具體觀察者的狀態 /// </summary> public string SubjectState { get { return subjectState; } set { subjectState = value; } } } //抽象觀察者類 public abstract class Observer { public abstract void Update(); } //具體觀察者 public class ConcreteObserver : Observer { private string observerState; private string name; private ConcreteSubject subject; /// <summary> /// 具體觀察者用一個具體主題來實現 /// </summary> public ConcreteSubject Subject { get { return subject; } set { subject = value; } } public ConcreteObserver(ConcreteSubject subject, string name) { this.subject = subject; this.name = name; } /// <summary> /// 實現抽象觀察者中的更新操作 /// </summary> public override void Update() { observerState = subject.SubjectState; Console.WriteLine("The observer's state of {0} is {1}", name, observerState); } } |
可以看到雖然已經很好的實現了觀察者Observer 和主題Subject之間的分離。但是Subject的內部還是有對觀察者的呼叫:
1 2 3 4 5 6 7 |
public void Notify() { foreach (Observer o in observers) { o.Update(); } } |
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 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 |
class Program3 { static void Main(string[] args) { // 具體主題角色通常用具體自來來實現 ConcreteSubject subject = new ConcreteSubject(); //傳入的只是觀察者的通過方法。 subject.Attach(new ConcreteObserver(subject, "Observer A").Update); subject.Attach(new ConcreteObserver(subject, "Observer B").Update); subject.Attach(new ConcreteObserver(subject, "Observer C").Update); subject.SubjectState = "Ready"; subject.Notify(); Console.Read(); } } public delegate void ObserverDelegate(); //抽象主題類 public abstract class Subject { public ObserverDelegate observedelegate; /// <summary> /// 增加觀察者 /// </summary> /// <param name="observer"></param> public void Attach(ObserverDelegate observer) { observedelegate += observer; } /// <summary> /// 移除觀察者 /// </summary> /// <param name="observer"></param> public void Detach(ObserverDelegate observer) { observedelegate -= observer; } /// <summary> /// 向觀察者(們)發出通知 /// </summary> public void Notify() { if (observedelegate != null) { observedelegate(); } } } //具體主題類 public class ConcreteSubject : Subject { private string subjectState; /// <summary> /// 具體觀察者的狀態 /// </summary> public string SubjectState { get { return subjectState; } set { subjectState = value; } } } //具體觀察者 public class ConcreteObserver { private string observerState; private string name; private ConcreteSubject subject; /// <summary> /// 具體觀察者用一個具體主題來實現 /// </summary> public ConcreteSubject Subject { get { return subject; } set { subject = value; } } public ConcreteObserver(ConcreteSubject subject, string name) { this.subject = subject; this.name = name; } /// <summary> /// 實現抽象觀察者中的更新操作 /// </summary> public void Update() { observerState = subject.SubjectState; Console.WriteLine("The observer's state of {0} is {1}", name, observerState); } } |
得到結果:
這樣設計的優勢:
(1)將通知的方法Update通過委託的形式傳入主題物件。這樣主題物件Subject就完全和觀察者隔離。更好地實現了低耦合。
(2)減少了觀察者抽象類的定義。使整個設計更加精簡。
(3)如果將設計更進一步,觀察者這邊自定義delegate void ObserverDelegate()這種型別的方法。比如需要執行Update()方法之後還要記錄一個日誌的操作。如:
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 |
//具體觀察者 public class ConcreteObserver { private string observerState; private string name; private ConcreteSubject subject; /// <summary> /// 具體觀察者用一個具體主題來實現 /// </summary> public ConcreteSubject Subject { get { return subject; } set { subject = value; } } public ConcreteObserver(ConcreteSubject subject, string name) { this.subject = subject; this.name = name; } /// <summary> /// 實現抽象觀察者中的更新操作 /// </summary> public void Update() { observerState = subject.SubjectState; Console.WriteLine("The observer's state of {0} is {1}", name, observerState); } public void Log() { Console.WriteLine("Log:Update方法執行完成"); } } |
那麼在客戶端呼叫時只需要將Log方法以委託的形式傳入即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
static void Main(string[] args) { // 具體主題角色通常用具體自來來實現 ConcreteSubject subject = new ConcreteSubject(); //傳入的只是觀察者的通過方法。 var obj = new ConcreteObserver(subject, "Observer A"); subject.Attach(obj.Update); subject.Attach(obj.Log); subject.SubjectState = "Ready"; subject.Notify(); Console.Read(); } |
是不是顯得更靈活一點。如果是純多型的方式,由於Subject裡面指定了呼叫Update()方法,所以當需要增加Log方法的時候程式碼的改變數要大。
三、模板方法模式,這裡就以裝置採集為例來進行說明:
1、多型實現模板方法模式:
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 |
class Program4 { static void Main(string[] args) { var oTem1 = new DeviceMML(); oTem1.Spider(); Console.WriteLine(""); var oTem2 = new DeviceTL2(); oTem2.Spider(); Console.ReadKey(); } } public abstract class TempleteDevice { // 模板方法,不要把模版方法定義為Virtual或abstract方法,避免被子類重寫,防止更改流程的執行順序 public void Spider() { Console.WriteLine("裝置採集開始"); this.Login(); this.Validation(); this.SpiderByType1(); this.SpiderByType2(); this.LoginOut(); Console.WriteLine("裝置採集結束"); } // 登陸 public void Login() { Console.WriteLine("登陸"); } // 驗證 public void Validation() { Console.WriteLine("驗證"); } // 採集 public abstract void SpiderByType1(); public abstract void SpiderByType2(); // 登出 public void LoginOut() { Console.WriteLine("登出"); } } //MML型別的裝置的採集 public class DeviceMML : TempleteDevice { public override void SpiderByType1() { Console.WriteLine("MML型別裝置開始採集1"); //....... } public override void SpiderByType2() { Console.WriteLine("MML型別裝置開始採集2"); } } //TL2型別裝置的採集 public class DeviceTL2 : TempleteDevice { public override void SpiderByType1() { Console.WriteLine("TL2型別裝置開始採集1"); //....... } public override void SpiderByType2() { Console.WriteLine("TL2型別裝置開始採集2"); } } |
父類裡面的非abstract方法都是模板方法,也就是子類公用並且不可以重寫的方法。SpiderType1和SpiderType2是需要子類重寫的方法。模板方法模式在抽象類中定義了演算法的實現步驟,將這些步驟的實現延遲到具體子類中去實現,從而使所有子類複用了父類的程式碼,所以模板方法模式是基於繼承的一種實現程式碼複用的技術。
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 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 |
class Program4 { static void Main(string[] args) { var oTem1 = new TempleteDevice(DeviceMML.SpiderByType1, DeviceMML.SpiderByType2); oTem1.Spider(); Console.WriteLine(""); var oTem2 = new TempleteDevice(DeviceTL2.SpiderByType1, DeviceTL2.SpiderByType2); oTem2.Spider(); Console.ReadLine(); } } public delegate void DeviceDelegate(); public class TempleteDevice { public DeviceDelegate oDelegate; public TempleteDevice(params DeviceDelegate[] lstFunc) { foreach (var oFunc in lstFunc) { oDelegate += oFunc; } } // 模板方法,不要把模版方法定義為Virtual或abstract方法,避免被子類重寫,防止更改流程的執行順序 public void Spider() { Console.WriteLine("裝置採集開始"); this.Login(); this.Validation(); if (oDelegate != null) { oDelegate(); } this.LoginOut(); Console.WriteLine("裝置採集結束"); } // 登陸 public void Login() { Console.WriteLine("登陸"); } // 驗證 public void Validation() { Console.WriteLine("驗證"); } // 登出 public void LoginOut() { Console.WriteLine("登出"); } } //MML型別的裝置的採集 public class DeviceMML { public static void SpiderByType1() { Console.WriteLine("MML型別裝置開始採集1"); //....... } public static void SpiderByType2() { Console.WriteLine("MML型別裝置開始採集2"); } } //TL2型別裝置的採集 public class DeviceTL2 { public static void SpiderByType1() { Console.WriteLine("TL2型別裝置開始採集1"); //....... } public static void SpiderByType2() { Console.WriteLine("TL2型別裝置開始採集2"); } } |
得到結果:
優化模板方法模式的意義:
(1)解除了子類和父類之間的繼承關係,更好地實現了物件間的低耦合。
(2)採用委託可以動態實現方法的組合,這種方式更加靈活,子類可以更加靈活的設計不同部分的方法。然後方法的數量通過params來傳遞,方法的數量沒有什麼嚴格的限制。
當然其他設計模式也可以使用委託去優化設計,博主在這裡就暫時只分享這三種模式的異同。總的來說,委託不可能代替多型去實現各種模式,但是它和多型聯合起來使用可以實現更加靈活的設計。通過這兩篇下來,不知道你是否對委託有點感覺了呢,委託這東西,重在實戰,就像游泳一樣,如果不用那麼幾次,你永遠也不可能學會。以上只是博主個人的理解,可能很多方便沒有考慮得那麼全面,希望各位園友拍磚斧正~~