閒話不多說,物件導向程式設計是高階語言的一個特點,但是把它概括成面向抽象更容易直擊靈魂,經過了菜鳥大家都要面對的是不要寫這麼菜的程式碼了。
上例子,這應該是大家都很熟悉耳熟能詳的程式碼, so easy。
1 using System; 2 using System.Diagnostics; 3 4 namespace ConsoleApp1 5 { 6 internal class Program 7 { 8 static void Main(string[] args) 9 { 10 Demo demo = new Demo(); 11 demo.PrintData(); 12 } 13 } 14 internal class Demo 15 { 16 private const int Max = 10; 17 private int[] Generate() 18 { 19 Random rnd = new Random(); 20 int[] data = new int[Max]; 21 for (int i = 0; i < Max; i++) 22 { 23 data[i] = rnd.Next() % 1023; 24 } 25 return data; 26 } 27 public void PrintData() 28 { 29 string result = string.Join(",", Array.ConvertAll<int, string>(Generate(), n => Convert.ToString(n))); 30 Trace.WriteLine(result); 31 Console.WriteLine(result); 32 } 33 } 34 }
我們看看它的脆弱性在哪裡?
•隨機數發生器可能變成從資料庫提取的一批商品數量或從多個下游企業發來的報文中篩選出來的RFID過檢(通過檢查)集裝箱件數。
•使用者可能還需要把資訊寫入資料庫、寫入檔案,或者覺得有些Viewer顯示沒什麼用處,只要一種Output視窗就可以了。
歸納一下,這種混合方式的程式相對脆弱,因為會導致變化的因素比較多,按照我們之前設計模式的經驗,這時候應該抽象物件,這裡我們先把V和M抽象出來,然後在C中組合它們:
using System; using System.Collections.Generic; using System.Diagnostics; namespace ConsoleApp2 { internal class Program { static void Main(string[] args) { Controller controller = new Controller(); controller.Model = new Randomizer(); controller += new TraceView(); controller += new ConsoleView(); controller.Process(); } } public class Controller { private IList<IView> views = new List<IView>(); private IModel model; public virtual IModel Model { get => model; set => model = value; } public void Process() { if (views.Count == 0) return; string result = string.Join(",", Array.ConvertAll<int, string>(model.Data, n => Convert.ToString(n))); foreach (var view in views) { view.Print(result); } } public static Controller operator +(Controller controller, IView view) { if (view == null) throw new ArgumentNullException("view"); controller.views.Add(view); return controller; } public static Controller operator -(Controller controller, IView view) { if (view == null) throw new ArgumentNullException("view"); controller.views.Remove(view); return controller; } } class Randomizer : IModel { public int[] Data { get { Random random = new Random(); int[] result = new int[10]; for (int i = 0; i < result.Length; i++) { result[i] = random.Next() % 1023; } return result; } } } class ConsoleView : IView { public void Print(string data) { Console.WriteLine(data); } } class TraceView : IView { public void Print(string data) { Trace.WriteLine(data); } } public interface IView { void Print(string data); } public interface IModel { int[] Data { get; } } }
按照上面的介紹,主動方式MVC需要一個觀察者對M保持關注。這裡我們簡單採用.NET的事件機制來充當這個角色,而本應從V發起的重新訪問M獲得新資料的過程也簡化為事件引數,在觸發事件的同時一併提供,程式碼如下所示:
using System; using System.Diagnostics; namespace ConsoleApp3 { internal class Program { static void Main(string[] args) { Controller controller = new Controller(); IModel model = new Model(); controller += new TraceView(); controller += new ConsoleView(); // 後續Model修改時,不經過Controller,而是經由觀察者完成View的變化 model[1] = 2000; // 第一次修改(修改的內容按照之前的隨機數計算不會出現) model[3] = -100; // 第二次修改(修改的內容按照之前的隨機數計算不會出現) } } internal class Controller { private IModel model; public virtual IModel Model { get => model; set => model = value; } public static Controller operator +(Controller controller, IView view) { if (view == null) throw new ArgumentNullException(nameof(view)); controller.Model.DataChanged += view.Handler; return controller; } public static Controller operator -(Controller controller, IView view) { if (view == null) throw new ArgumentNullException(nameof(view)); controller.Model.DataChanged -= view.Handler; return controller; } } internal class Model : IModel { public event EventHandler<ModelEventArgs> DataChanged; private int[] data; public int this[int index] { get => data[index]; set { this.data[index] = value; DataChanged?.Invoke(this, new ModelEventArgs(data)); } } public Model() { Random rnd = new Random(); data = new int[10]; for (int i = 0; i < data.Length; i++) { data[i] = rnd.Next() % 1023; } } } internal abstract class ViewBase : IView { public abstract void Print(string data); public virtual void OnDataChanged(object sender, ModelEventArgs args) { Print(args.Context); } public virtual EventHandler<ModelEventArgs> Handler { get => this.OnDataChanged; } } internal class TraceView : ViewBase { public override void Print(string data) { Trace.WriteLine(data); } } internal class ConsoleView : ViewBase { public override void Print(string data) { Console.WriteLine(data); } } internal class ModelEventArgs : EventArgs { private string content; public string Context { get => this.content; } public ModelEventArgs(int[] data) { content = string.Join(",", Array.ConvertAll<int, string>(data, n => Convert.ToString(n))); } } internal interface IModel { event EventHandler<ModelEventArgs> DataChanged; int this[int index] { get; set; } } internal interface IView { EventHandler<ModelEventArgs> Handler { get; } void Print(string data); } }
從上面的示例不難看出,相對被動方式的MVC而言,採用.NET事件方式實現主動方式有下述優勢:
•結構更加鬆散耦合,M/V之間可以沒有直接的依賴關係,組裝過程可以由C完成,M/V之間的觀察者僅經由.NET標準事件和委託進行互動。
•不用設計獨立的觀察者物件。
•由於C不需參與M資料變更後實際的互動過程,因此C也無需設計用來儲存V的容器。
•如果EventArgs設計合理的話,可以更自由地與其他產品或第三方物件體系進行整合。
注: 摘錄於 王翔 《設計模式 基於c#的工程化實現及擴充套件》 摘自其中一節,2008出版的版本。新的版本改動很大,沒有那麼喜歡了。推薦有興趣的自行剁手,一頓飯的錢。這本書的質量本人覺得非常高,雖然2008的年出版,但是放到現在再結合現代的技術發展更加印證了這本書是經得起時間考驗的。