設計模式-觀察者模式


定義

觀察者模式(有時又被稱為發布-訂閱Subscribe>模式、模型-視圖View>模式、源-收聽者Listener>模式或從屬者模式)是軟件設計模式的一種。在此種模式中,一個目標物件管理所有相依於它的觀察者物件,並且在它本身的狀態改變時主動發出通知。這通常透過呼叫各觀察者所提供的方法來實現。此種模式通常被用來實現事件處理系統。 

基本簡介

觀察者模式(Observer)完美的將觀察者和被觀察的對象分離開。舉個例子,用戶界面可以作為一個觀察者,業務數據是被觀察者,用戶界面觀察業務數據的變化,發現數據變化后,就顯示在界面上。面向對象設計的一個原則是:系統中的每個類將重點放在某一個功能上,而不是其他方面。一個對象只做一件事情,並且將他做好。觀察者模式在模塊之間划定了清晰的界限,提高了應用程序的可維護性和重用性。
觀察者設計模式定義了對象間的一種一對多的依賴關系,以便一個對象的狀態發生變化時,所有依賴於它的對象都得到通知並自動刷新。
 

(1)抽象主題(Subject)

 持有一個觀察者對象的集合,提供增加,刪除觀察者對象的接口,當需要關注的狀態變化時候,需要通知所有持有的觀察者對象。

(2)具體主題(Concrete Subject

 被觀察者的具體實現...

(3)抽象觀察者(Observer

 定義一個接口,在接受通知時候更新狀態

(4)具體觀察者Concrete Observer

 觀察者的具體實現...

 

UML圖

 

示例

 (1.1)抽象主題類Subject

    /// <summary>
    /// 抽象主題
    /// </summary>
    public abstract class Subject
    {
        /// <summary>
        /// 所有觀察者對象
        /// </summary>
        private List<Observer> observers = new List<Observer>();

        /// <summary>
        /// 增加觀察者對象
        /// </summary>
        /// <param name="observer"></param>
        public void AddObserver(Observer observer)
        {
            observers.Add(observer);
        }

        /// <summary>
        /// 移除觀察者對象
        /// </summary>
        /// <param name="observer"></param>
        public void RemoveObserver(Observer observer)
        {
            observers.Remove(observer);
        }

        /// <summary>
        /// 發送通知
        /// </summary>
        public void Notify()
        {
            foreach (var ob in observers)
            {
                ob.Update();
            }
        }
    }

(1.2)具體主題類ConcreteSubject

    /// <summary>
    /// 具體通知者
    /// </summary>
    public class ConcreteSubject : Subject
    {
        /// <summary>
        /// 具體觀察者狀態
        /// </summary>
        public string SubjectState { get; set; }
    }

(1.3)抽象觀察者類

   /// <summary>
    /// 觀察者類
    /// </summary>
    public abstract class Observer
    {
        public abstract void Update();
    }

(1.4)具體觀察者類

    /// <summary>
    /// 具體觀察者
    /// </summary>
    public class ConcreteObserver : Observer
    {
        public string observerState { get; set; }
        public string Name { get; set; }
        public ConcreteSubject subjcSubject { get; set; }

        public ConcreteObserver(ConcreteSubject subject, string name)
        {
            this.Name = name;
            this.subjcSubject = subject;
        }

        public override void Update()
        {
            observerState = subjcSubject.SubjectState;
            System.Console.WriteLine("the observer's state of {0} is {1}", Name, observerState);
        }
    }

(1.5)控制台調用

    class Program
    {
        static void Main(string[] args)
        {
            ConcreteSubject subject = new ConcreteSubject();

            subject.AddObserver(new ConcreteObserver(subject, "ObA"));
            subject.AddObserver(new ConcreteObserver(subject, "ObB"));
            subject.AddObserver(new ConcreteObserver(subject, "ObC"));

            subject.SubjectState = "Ready";
            subject.Notify();

            System.Console.ReadKey();
        }
    }

(1.6)運行結果

 

優缺點

優點:
1、 Subject和Observer之間是松耦合的,分別可以各自獨立改變。
2、 Subject在發送廣播通知的時候,無須指定具體的Observer,Observer可以自己決定是否要訂閱Subject的通知。
3、 遵守大部分GRASP原則和常用設計原則,高內聚、低耦合。
缺陷:
1、  依賴關系並未完全解除,抽象通知者依舊依賴抽象的觀察者。
2、 如果一個Subject被大量Observer訂閱的話,在廣播通知的時候可能會有效率問題。
 
思考:抽象已經降低Subject和Observer之間的耦合度,但是他們之間依舊存在依賴。而我們觀察者模式一定程度上注重着對觀察者的通知,也就是動作的傳遞,目前Subject中依靠維持抽象具體觀察對象,在發送通知時候循環每個具體觀察者,調用通知方法,那么最終我們的目的就是主題能調用相應的通知方法。換個角度說我們是不是可以維護一個方法列表,那么C#中的委托(多播委托)似乎可以實現我們的這一目標。

 

C#委托改版

我們以一個主題通知發送消息的例子來演示

(1.1)抽象主題

    /// <summary>
    /// 抽象主題
    /// </summary>
    public interface ISubject
    {
        void Notify();
    }

(1.2)聲明委托和具體主題

    /// <summary>
    /// 聲明委托
    /// </summary>
    public delegate void MsgEvent();
    /// <summary>
    /// 具體主題
    /// </summary>
    public class ConcreteSubject : ISubject
    {

        /// <summary>
        /// 定義委托事件
        /// </summary>
        public event MsgEvent MsgAction;

        /// <summary>
        /// 執行通知
        /// </summary>
        public void Notify()
        {
            if (MsgAction != null)
                MsgAction();
        }
    }

(1.3)添加具體觀察者 站內信,郵件,短信

    /// <summary>
    /// 站內信
    /// </summary>
    public class InsideLetterMsg
    {
        /// <summary>
        /// 發送站內信
        /// </summary>
        public void SendInsideLetterMsg()
        {
            Console.WriteLine("發送站內信.....");
        }
    }

    /// <summary>
    /// 郵件
    /// </summary>
    public class MailMsg
    {
        /// <summary>
        /// 發送郵件
        /// </summary>
        public void SendMailMsg()
        {
            Console.WriteLine("發送郵件.....");
        }
    }

    /// <summary>
    /// 短信
    /// </summary>
    public class SMSMsg
    {
        /// <summary>
        /// 發送短信
        /// </summary>
        public void SendSMSMsg()
        {
            Console.WriteLine("發送短信.....");
        }
    }

(1.4)客戶端調用

    class Program
    {
        static void Main(string[] args)
        {
            ConcreteSubject subject = new ConcreteSubject();

            //注冊事件
            subject.MsgAction += (new InsideLetterMsg()).SendInsideLetterMsg;  //站內信
            subject.MsgAction += (new MailMsg()).SendMailMsg; //郵件
            subject.MsgAction += (new SMSMsg()).SendSMSMsg;   //短信

            //開始發送消息了
            subject.Notify();   

            Console.ReadKey();

        }
    }

(1.5)結果

 

這個例子我們更多關注的是行為,主題對於觀察者行為的執行和通知。我們只需要在調用的時候將觀察者的方法注冊到主題中即可。省去了主題需要維護觀察者對象,循環調用觀察者對象方法的過程。順帶我們也看一下我們創建的委托具體是what?

 

委托看一看

(1.1)看圖說話

(1.2)System.MulticastDelegate

  

   _invocationList通常這個字段為null,當我們構造一個委托鏈是,他可以引用一個委托數組,也就是說我們給委托+=方法時候,實際是操作它.

   我們可以推斷出當我們調用委托方法時候,代碼大致是這樣的(下面代碼不是可運行的代碼,只是預估大概的邏輯,僅供觀看),不知道在觀察者這塊順帶寫了下委托是不是有點

   跑偏,委托這里只是順帶提一下,更多的知識面肯定沒涉及到,只是幫助大家理解下.

            //從委托鏈中獲取
            Delegate[] deleagetset = _invocationList as Delegate[];
            if (deleagetset != null)
            {
                foreach (MsgAction m in deleagetset)
                {
                    m(value); //調用每個委托
                }
            }
            else 
            {
                //當前不是委托鏈 直接 invoke
                _methodPtr.Invoke(_target, value);
            }

 上邊的例子只是單純的循環,中途有一個調用委托失敗都沒有健壯的處理,所以MulticastDelegate類提供了另一個實例方法GetInvocationList,用於顯示調用鏈中的每一個委托,具體大家可以查閱相關資料。That's all!

 

文章代碼鏈接http://files.cnblogs.com/files/mongo/BlogObserver.zip

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM