設計模式 - 觀察者模式

  • 1244
  • 0

此篇簡單介紹觀察者模式。

關注在 :

  • 如何降低 "主題"與"觀察者" 之間的耦合
  • 聚焦於高階模組與低階模組所依賴的抽象,而不是實作類別

要點 :

  • 適度的使用介面
  • 將演算法中可能的變化獨立出來

 

類別圖 : 

 

主題 - 抽象類 : 

using System;
using System.Collections.Generic;
using ObserverPatternSample.Observer;

namespace ObserverPatternSample.Subject
{
    #region 所有主題都應繼此介面(Interface)
    /// <summary>
    /// 所有主題都應繼此介面(Interface)
    /// </summary>
    internal interface ISubject
    {
        /// <summary>
        /// 新增觀察者
        /// </summary>
        /// <param name="observer">抽象的觀察者,不需知道其具體類別為何、實作的細節...</param>
        void attach(Observer.IObserver observer);

        /// <summary>
        /// 移除觀察者
        /// </summary>
        /// <param name="observer">抽象的觀察者,不需知道其具體類別為何、實作的細節...</param>
        void detach(Observer.IObserver observer);

        /// <summary>
        /// 當主題發生變化時,通知觀察者
        /// </summary>
        void notifyObserver();

        /// <summary>
        /// 取得主題狀態
        /// </summary>
        /// <returns>回傳當前主題狀態</returns>
        string getState();
    }
    #endregion
}

 

 

主題 -  實作類 : 

using System;
using System.Collections.Generic;
using ObserverPatternSample.Observer;
using System.Linq;

namespace ObserverPatternSample.Subject
{
    #region ConcreteSubject 主題實作類
    /// <summary>
    /// 儲存向該主題(ConcretSubjectA)註冊的觀察者集合的參考
    /// </summary>
    sealed internal class ConcretSubjectA : ISubject
    {
        /// <summary>
        /// 此為主題的內容
        /// </summary>
        private string _subjectContext = String.Empty;

        /// <summary>
        /// 使用List<>儲存向該主題註冊的觀察者
        /// </summary>
        private List<Observer.IObserver> _observers = null;

        public ConcretSubjectA()
        {
            // 初始化 _observers
            this._observers = new List<Observer.IObserver>();
        }

        /// <summary>
        /// 取得主題狀態
        /// </summary>
        /// <returns>回傳當前主題狀態</returns>
        public string getState()
        {
            if (String.IsNullOrEmpty(this._subjectContext)) return "主題內容未設定";
            return this._subjectContext;
        }

        /// <summary>
        /// 設定主題內容
        /// </summary>
        /// <param name="stateText">輸入內容字串</param>
        public void setSubjectContext(string setText)
        {
            this._subjectContext = "設定主題內容: " + setText;
            Console.WriteLine(this.GetType().Name+" 變更狀態");
            Console.WriteLine(this._subjectContext);
            Console.WriteLine();

            // Note : 也可以在更新狀態後直接對各個使用者做更新
        }

        /// <summary>
        /// 新增觀察者
        /// </summary>
        /// <param name="observer">抽象的觀察者,不需知道其具體類別為何、實作的細節...</param>
        public void attach(IObserver observer)
        {
            if (observer != null) this._observers.Add(observer);
        }

        /// <summary>
        /// 移除觀察者
        /// </summary>
        /// <param name="observer">抽象的觀察者,不需知道其具體類別為何、實作的細節...</param>
        public void detach(IObserver observer)
        {
            if (observer != null)
                if (this._observers.Contains(observer)) this._observers.Remove(observer);
        }

        /// <summary>
        /// 當主題發生變化時,通知觀察者
        /// </summary>
        public void notifyObserver()
        {
            Console.WriteLine("----------------------------------------");
            Console.WriteLine("| " + this.GetType().Name + " 通知給所有觀察者更新 |");
            Console.WriteLine("----------------------------------------");
            Console.WriteLine("                    ▼");
            Console.WriteLine();

            // 使用Lamdba 原理大致上就是使用forEach探索List的集合後,使用指定的方法
            if (this._observers.Any()) this._observers.ForEach(x => x.update());
                else Console.WriteLine("無觀察者註冊該主題");
            Console.WriteLine();
        }
    }
    #endregion
}

 

 

觀察者 - 抽象類 : 

using System;

namespace ObserverPatternSample.Observer
{
    #region 所有觀察者都應繼承此介面(interface)
    /// <summary>
    /// 所有觀察者都應繼承此介面(interface)
    /// </summary>
    interface IObserver
    {
        // int myProperty { get; }

        /// <summary>
        /// 供 主題 狀態改變時予以通知每個有註冊的 觀察者
        /// </summary>
        void update();
    }
    #endregion
}

 

觀察者 - 實作類 : 

using System;

namespace ObserverPatternSample.Observer
{
    #region ConcreteObserver 觀察者實作類
    /// <summary>
    /// 實例化各個觀察者 且實作 IObserver 的方法
    /// </summary>
    sealed internal class ConcreteObserverA : IObserver
    {
        // public int myProperty{
        //     get{
        //         throw new NotImplementedException();
        //     }
        //     private set{
        //     }
        // }

        /// <summary>
        /// 此為從主題更新的內容
        /// </summary>
        private string _observerContext = String.Empty;

        /// <summary>
        /// 儲存對應主題的參考
        /// </summary>
        private Subject.ISubject _concretSubject = null;

        /// <summary>
        /// 初始化需要有主題的引數傳入
        /// </summary>
        /// <param name="concreteSubject"> 請傳入主題(Subject) </param>
        public ConcreteObserverA(Subject.ISubject concreteSubject)
        {
            this._concretSubject = concreteSubject;
        }

        /// <summary>
        /// 從主題得知改變內容後更新自身內容且實作
        /// </summary>
        public void update()
        {
            // 從主題得知改變內容後更新自身內容
            this._observerContext = this._concretSubject.getState();
            Console.WriteLine(this.GetType().Name+" 收到通知");
            Console.WriteLine("觀察者更新內容: "+this._observerContext);
            Console.WriteLine();
        }
    }
    #endregion
}

 

 

用戶端 : 

using System;

namespace ObserverPatternSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // 實例主題
            Subject.ConcretSubjectA subject1 = new Subject.ConcretSubjectA();
            // 設置主題內容
            subject1.setSubjectContext($"初始內容: 第一次更動內容");

            // 使用抽象方式實例觀察者並初始化
            Observer.IObserver observer1 = new Observer.ConcreteObserverA(subject1);
            Observer.IObserver observer2 = new Observer.ConcreteObserverA(subject1);
            Observer.IObserver observer3 = new Observer.ConcreteObserverA(subject1);

            // 觀察者訂閱主題並註冊
            subject1.attach(observer1);
            subject1.attach(observer2);
            subject1.attach(observer3);
            // 變更主題內容
            subject1.setSubjectContext("變更內容: 第二次更動內容");
            // 通知已註冊的觀察者並更新
            subject1.notifyObserver();

            Console.WriteLine("觀察者取消註冊");
            // 觀察者取消註冊
            subject1.detach(observer1);
            // 主題更新
            subject1.notifyObserver();
            Console.Read();
        }
    }
}

 

結果顯示 : 

 


多多指教!! 歡迎交流!!

你不知道自己不知道,那你會以為你知道