C#面向抽象程式設計第二講

星仔007發表於2022-04-12

抽象程式設計怎麼說呢,以觀察者模式為例:

觀察者模式有兩個物件,一個是觀察者,一個是可觀察者(字面翻譯很彆扭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();

//這裡學生是多個,也定義可以多個老師,實現多對多關係

 

示例程式碼:

exercise/釋出訂閱And出版預定_EventBus_Observer/Observer/Observer at master · liuzhixin405/exercise (github.com)

相關文章