抽象程式設計怎麼說呢,以觀察者模式為例:
觀察者模式有兩個物件,一個是觀察者,一個是可觀察者(字面翻譯很彆扭observable),訊息釋出者(提供者)。
第一層如下,三個物件A、B、C分別有一個接收訊息的方法,還有一個儲存資料的欄位,X就是釋出訊息的物件,它通過setdata方法設定自己的欄位data,然後通知abc,abc如願以償地拿到了通知,完美!
internal class A { public int Data; public void Update(int data) { this.Data = data; } } internal class B { public int Count; public void Notify(int data) { this.Count = data; } } internal class C { public int N; public void Set(int data) { this.N = data; } } internal class X { private int data; public A instanceA; public B instanceB; public C instanceC; public void SetData(int data) { this.data = data; instanceA.Update(data); instanceB.Notify(data); instanceC.Set(data); } } using ObserverOne; A a = new A(); B b = new B(); C c = new C(); Console.WriteLine("訂閱前................."); Console.WriteLine($"a.Data = {a.Data}"); Console.WriteLine($"b.Count = {b.Count}"); Console.WriteLine($"c.N = {c.N}"); X x =new X(); x.instanceA = a; x.instanceB = b; x.instanceC = c; x.SetData(10); Console.WriteLine("X釋出data=10, 訂閱後................."); Console.WriteLine($"a.Data = {a.Data}"); Console.WriteLine($"b.Count = {b.Count}"); Console.WriteLine($"c.N = {c.N}");
再想一想,這好像不夠靈活,訂閱者是死的,那改進一下:
internal interface IUpdatebleObject { int Data { get; } void Update(int newData); } internal class A : IUpdatebleObject { public int Data => data; private int data; public void Update(int newData) { this.data = newData; } } internal class B : IUpdatebleObject { public int Data => data; private int data; public void Update(int newData) { this.data = newData; } } internal class C : IUpdatebleObject { public int Data => data; private int data; public void Update(int newData) { this.data = newData; } } internal class X { private IUpdatebleObject[] updates=new IUpdatebleObject[3]; public IUpdatebleObject this[int index] { set { updates[index] = value; } } private int data; public void Update(int newData) { this.data = newData; foreach (var update in updates) { update.Update(newData); } } } using ObserverTwo; X x = new X(); IUpdatebleObject a = new A(); IUpdatebleObject b = new B(); IUpdatebleObject c = new C(); Console.WriteLine("訂閱前................."); Console.WriteLine($"a.Data = {a.Data}"); Console.WriteLine($"b.Data = {b.Data}"); Console.WriteLine($"c.Data = {c.Data}"); x[0] = a; x[1] = b; x[2] = c; x.Update(10); Console.WriteLine("X釋出data=10, 訂閱後................."); Console.WriteLine($"a.Data = {a.Data}"); Console.WriteLine($"b.Data = {b.Data}"); Console.WriteLine($"c.Data = {c.Data}");
雖然寫到這個例子已經很了不起了,但是對於有想法的來說還是可以繼續改進,要不然怎麼常掛嘴邊說面對抽象程式設計呢,那就繼續改進了:
/// <summary> /// 觀察者 /// </summary> /// <typeparam name="T"></typeparam> internal interface IObserver<T> { void Update(SubjectBase<T> subject); }
/// <summary> /// 可觀察者(發出通知的物件) /// </summary> /// <typeparam name="T"></typeparam> internal abstract class SubjectBase<T> { protected IList<IObserver<T>> observers = new List<IObserver<T>>(); protected T state; public virtual T State => state; public static SubjectBase<T> operator +(SubjectBase<T> subject,IObserver<T> observer) { subject.observers.Add(observer); return subject; } public static SubjectBase<T> operator -(SubjectBase<T> subject,IObserver<T> observer) { subject.observers.Remove(observer); return subject; } public virtual void Notify() { foreach (var observer in observers) { observer.Update(this); } } public virtual void Update(T state) { this.state = state; Notify(); } }
internal class Observer<T> : IObserver<T> { public T State; public void Update(SubjectBase<T> subject) { this.State = subject.State; } }
internal class Subject<T>:SubjectBase<T> { }
到這裡基本上可以說是把骨架搭起來了,這些可以稱之為底層的程式碼。實現程式碼如下:
internal class TestObserver { public void TestMulticst() { SubjectBase<int> subject = new Subject<int>(); Observer<int> observer1 = new Observer<int>(); observer1.State = 10; Observer<int> observer2 = new Observer<int>(); observer2.State = 20; subject += observer1; subject += observer2; subject.Update(1); Console.WriteLine($"observer1.State={observer1.State} observer2.State={observer2.State}"); subject -= observer1; subject.Update(100); Console.WriteLine($"update state = 100, observer1.State={observer1.State} observer2.State={observer2.State}"); } public void TestMultiSubject() { SubjectBase<string> subject1 = new Subject<string>(); SubjectBase<string> subject2 = new Subject<string>(); Observer<string> observer1 = new Observer<string>(); observer1.State = "運動"; Console.WriteLine($"observer1.State={observer1.State}"); subject1 += observer1; subject2 += observer1; subject1.Update("看電影"); Console.WriteLine($"observer1.State={observer1.State}"); subject2.Update("喝茶"); Console.WriteLine($"observer1.State={observer1.State}"); subject1 -= observer1; subject2 -= observer1; observer1.State = "休息"; subject1 -= observer1; subject2 -= observer1; Console.WriteLine($"observer1.State={observer1.State}"); } }
using ObserverThree; //new TestObserver().TestMulticst(); new TestObserver().TestMultiSubject();
到這裡基本上就完成了任務,也就可以結束了。但是,學習需要深度也需要寬度,所以觀察者模式在C#可以通過事件來實現一樣的效果。下面就看下上面寫這麼多的程式碼用事件怎麼寫呢,這裡的例項稍作變化,實現改變名字通知觀察者,這裡觀察者就是控制檯了,列印通知:
internal class UserEventArgs:EventArgs { private string name; public string Name => name; public UserEventArgs(string name) { this.name = name; } }
internal class User { public event EventHandler<UserEventArgs> NameChanged; private string name; public string Name { get { return name; } set { name = value; NameChanged?.Invoke(this, new UserEventArgs(value)); } } }
using ObserverFour; User user = new User(); user.NameChanged += OnNameChanged; user.Name = "joe"; void OnNameChanged(object sender, UserEventArgs args) { Console.WriteLine($"{args.Name} Changed "); }
再放一個麻煩一點的例子,字典新增的通知(監聽)事件:
internal class DictionaryEventArgs<TKey,TValue> : EventArgs { private TKey key; private TValue value; public DictionaryEventArgs(TKey key,TValue value) { this.key = key; this.value = value; } public TKey Key => key; public TValue Value => value; }
internal interface IObserverableDictionary<TKey,TValue>:IDictionary<TKey, TValue> { EventHandler<DictionaryEventArgs<TKey,TValue>> NewItemAdded { get; set; } }
internal class ObserverableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IObserverableDictionary<TKey, TValue> { protected EventHandler<DictionaryEventArgs<TKey, TValue>> newItemAdded; public EventHandler<DictionaryEventArgs<TKey, TValue>> NewItemAdded { get => newItemAdded;set=> newItemAdded = value;} public new void Add(TKey key,TValue value) { base.Add(key, value); if(NewItemAdded != null) NewItemAdded(this, new DictionaryEventArgs<TKey, TValue>(key, value)); } }
using ObserverFive; string key = "hello"; string value = "world"; IObserverableDictionary<string,string> dictionary = new ObserverableDictionary<string,string>(); dictionary.NewItemAdded += Validate; dictionary.Add(key, value); void Validate(object sender, DictionaryEventArgs<string,string> args) { Console.WriteLine($"{args.Key} {args.Value}"); }
事件說完了!再回頭看看觀察者設計模式。
微軟已經很重視觀察者模式這個設計,把IObserver、IObservable整合到runtime裡面去了,也就是基類庫裡面。aspnetcore框架也有用到這個,比如日誌模組。所以感覺有必要了解一下,放個小例子作為結束:
internal class Message { public string Notify { get; set; } }
internal class Teacher : IObservable<Message> { private readonly List<IObserver<Message>> _observers; public Teacher() { _observers = new List<IObserver<Message>>(); } public IDisposable Subscribe(IObserver<Message> observer) { _observers.Add(observer); return new Unsubscribe(observer, _observers); } public void SendMessage(string message) { foreach (var observer in _observers) { observer.OnNext(new Message() { Notify = "message" }); } } public void OnCompleted() { foreach (var observer in _observers) { observer.OnCompleted(); } _observers.Clear(); } } internal class Unsubscribe:IDisposable { private readonly IObserver<Message> _observer; private readonly List<IObserver<Message>> _observers; public Unsubscribe(IObserver<Message> observer, List<IObserver<Message>> observers) { this._observers = observers; this._observer = observer; } public void Dispose() { if(_observers.Contains(_observer)) _observers.Remove(_observer); } }
internal abstract class Student : IObserver<Message> { private string name; public Student(string name) { this.name = name; } private IDisposable _unsubscribe; public virtual void OnCompleted() { Console.WriteLine("放學了..."); } public virtual void OnError(Exception error) { Console.WriteLine("生病了..."); } public virtual void OnNext(Message value) { Console.WriteLine($"大家好: 我是 {name} -_- "); Console.WriteLine($"老師說:{value.Notify}"); } public virtual void Subscribe(IObservable<Message> obserable) { if (obserable != null) _unsubscribe = obserable.Subscribe(this); } }
internal class StudentZhang : Student { public StudentZhang(string name) : base(name) { } } internal class StudentLi : Student { public StudentLi(string name) : base(name) { } }
using ObserverSeven; Teacher teacher = new Teacher(); teacher.Subscribe(new StudentLi("李逵")); teacher.Subscribe(new StudentZhang("張麻子")); teacher.SendMessage("明天放假"); teacher.OnCompleted(); //這裡學生是多個,也定義可以多個老師,實現多對多關係
示例程式碼: