天天看點

(42)C#設計模式——觀察者模式(Observer Pattern)

定義

從生活中的例子可以看出,隻要對訂閱号關注的用戶端,如果訂閱号有什麼更新,就會直接推送給訂閱了的用戶端。從中,我們可以了解觀察者模式的定義。

觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽一個主題對象,這個主題對象在狀态發生改變時,會通知所有觀察者對象,使它們能夠自動更新自己的行為。

結構

從上面觀察者模式的定義和生活中的例子,我們知道了觀察者模式中首先會存在兩個對象,一個是觀察者對象,另一個就是主題對象(被觀察的對象),然而,根據面向接口程式設計的原則,自然就有抽象主題角色和抽象觀察者角色。要想主題對象狀态發生改變時,能通知到所有觀察者角色,則主題角色必須擁有觀察者的引用。

(42)C#設計模式——觀察者模式(Observer Pattern)

可以看出,觀察者模式的結構圖有以下角色:

  • 抽象主題角色(Subject):抽象主題把所有觀察者對象的額引用儲存在一個清單中,并提供增加和删除觀察者對象的操作,抽象主題角色又叫做被觀察者角色,一般由抽象類或接口實作。
  • 抽象觀察者角色(Observer):為所有具體觀察者定義一個接口,在的到主題通知時更新自己,一般由抽象類或接口實作
  • 具體主題角色(ConcreteSubject):實作抽象主題角色,具體主題角色又叫做具體被觀察者角色
  • 具體觀察者角色(ConcreteObserver):實作抽象觀察者角色所要求的接口,以便使自身狀态與主題的狀态相協調

實作

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    //騰訊遊戲訂閱号類
    public class TenxunGame
    {
        //訂閱對象
        public Subscriber Subscriber { get; set; }
        public string Symbol { get; set; }
        public string Info { get; set; }
        public void Update()
        {
            if(Subscriber != null)
            {
                //調用訂閱者對象來通知訂閱者
                Subscriber.ReceiveAndPrintData(this);
            }
        }
    }
    //訂閱者類
    public class Subscriber
    {
        public string Name { get; set; }
        public Subscriber(string name)
        {
            this.Name = name;
        }
        public void ReceiveAndPrintData(TenxunGame txGame)
        {
            Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, txGame.Symbol, txGame.Info);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Subscriber Bob = new Subscriber("Bob");
            TenxunGame txGame = new TenxunGame();

            txGame.Subscriber = Bob;
            txGame.Symbol = "TenXun Games";
            txGame.Info = "Wo had published a new game todday.......";
            txGame.Update();
        }
    }
}
           

上面的代碼實作了監控訂閱号的任務。但存在問題:

  1. TenxunGame和Subscriber類之間形成了一種雙向依賴關系,即TenxunGame調用了Subscriber的ReceiveAndPrintData方法,而Subscriber調用了TenxunGame類的屬性。這樣的實作,如果有其中一個類變化将引起另外一個類的變化
  2. 當出現一個新的訂閱者時,此時不得不修改TenxunGame代碼,即添加另外一個訂閱者的引用和在Update方法中調用另一個訂閱者的方法

上面的設計違背了“開閉原則。對此我們進一步抽象,既然這裡變化的部分是新訂閱者的出現,這樣我們可以對訂閱者抽象出一個接口,用它來取消TenxunGame類與具體訂閱者之間的依賴,這一步的改進,确實可以解決問題,但還不能解決出現一個新訂閱者就要修改TenxunGame代碼的問題。是以,進一步思考——訂閱号存在多個訂閱者,我們可以采用一個清單來儲存所有訂閱者對象,在訂閱号内部在添加對該清單的操作,這樣不就解決了出現了新的訂閱者的問題了。并且訂閱号也屬于變化的部分,所有,我們可以采用相同的方式抽象訂閱号,抽象成訂閱号類。修改後的實作為:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    
    //訂閱号抽象類
    public abstract class TenXun
    {
        //儲存訂閱者清單
        private List<IObserver> observers = new List<IObserver>();

        public string Symbol;
        public string Info;
        public TenXun(string symbol, string info)
        {
            this.Info = info;
            this.Symbol = symbol;
        }
        #region 新增對訂閱者清單的添加和删除操作
        public void AddObserver(IObserver ob)
        {
            observers.Add(ob);
        }
        public void RemoveObserver(IObserver ob)
        {
            observers.Remove(ob);
        }
        #endregion
        public void Update()
        {
            //周遊訂閱者進行通知
            foreach (IObserver ob in observers)
            {
                if (ob != null)
                {
                    ob.ReceiveAndPrintData(this);
                }
            }
        }
    }
    //具體訂閱号類
    public class TenxunGame : TenXun
    {
        public TenxunGame(string symbol, string info) : base(symbol, info)
        {

        }
    }
    //訂閱者接口
    public interface IObserver
    {
        void ReceiveAndPrintData(TenXun tenxun);
    }
    //具體訂閱者類
    public class Subscriber : IObserver
    {
        public string Name { get; set; }
        public Subscriber(string name)
        {
            this.Name = name;
        }
        public void ReceiveAndPrintData(TenXun tenxun)
        {
            Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, tenxun.Symbol, tenxun.Info);
        }
    }
    //用戶端測試
    class Program
    {
        static void Main(string[] args)
        {
            TenXun tenXun = new TenxunGame("TenxunGame", "Have a new game published...");
            //添加訂閱者
            tenXun.AddObserver(new Subscriber("Bob"));
            tenXun.AddObserver(new Subscriber("Lily"));
            tenXun.AddObserver(new Subscriber("Tom"));
            tenXun.Update();       
        }
    }
}
           

适用場景

  • 當一個抽象模型有兩個方面,其中一個方面依賴另一個方面,将這兩者封裝在獨立的對象中以使用它們可以各自獨立地改變和複用的情況下。
  • 當一個對象的改變需要同時改變其他對象,而又不知道具體有多少對象有待改變的情況下
  • 當一個對象必須通知其他對象,而又不能假定其他對象是誰的情況下

優缺點

優點:

  • 觀察者模式實作了表示層和資料邏輯層的分離,并定義了穩定的更新消息傳遞機制,并抽象了更新接口,使得可以有各種各樣不同的表示層,即觀察者。
  • 觀察者模式在被觀察者和觀察者之間建立了一個抽象的耦合,被觀察者并不知道任何一個具體的觀察者,隻是儲存着抽象觀察者的清單,每個具體觀察者都符合一個抽象觀察者的接口。
  • 觀察者模式支援廣播通信。被觀察者會向所有的注冊過的觀察者發出通知。

缺點:

  • 如果一個被觀察者有很多直接和間接的觀察者時,将所有的觀察者都通知到會花費很多時間。
  • 雖然觀察者模式可以随時使觀察者知道所觀察的對象發送了變化,但是觀察者模式沒有相應的機制使觀察者知道所觀察的對象是怎樣發生變化的。
  • 如果在被觀察者之間有循環依賴的話,被觀察者會觸發它們之間進行循環調用,導緻系統崩潰,在使用觀察者模式應特别注意這點。

總結

觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象可以同時監聽一個主圖對象,這個主題對象發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。解決的是“當一個對象的改變需要同時改變多個其他對象”的問題。

繼續閱讀