天天看點

設計模式之觀察者模式

什麼是觀察者模式?

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

結構:

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

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

設計分析及代碼實作

我們以監控QQ遊戲公衆号的消息推送為例展開讨論。

First:

namespace DesignPattern.Observer.First
{
    /// <summary>
    /// QQ遊戲公衆号
    /// </summary>
    public class QQGame
    {
        /// <summary>
        /// 訂閱對象
        /// </summary>
        public Subscriber Subscriber { get; set; }

        public string Symbol { get; set; }

        public string Info { get; set; }

        public QQGame(string symbol,string info)
        {
            this.Symbol = symbol;
            this.Info = info;
        }

        public void Update()
        {
            if (Subscriber!=null)
            {
                Subscriber.ReceiveAndPrintData(this);
            }
        }
    }
}

namespace DesignPattern.Observer.First
{
    /// <summary>
    /// 訂閱者類
    /// </summary>
    public class Subscriber
    {
        public string Name { get; set; }

        public Subscriber(string name)
        {
            this.Name = name;
        }

        public void ReceiveAndPrintData(QQGame game)
        {
            Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, game.Symbol, game.Info);
        }
    }
}

class Program
{
    /// <summary>
    /// 調用
    /// </summary>
    /// <param name="args"></param>
    static void Main(string[] args)
    {
        // 第一種方式
        Observer.First.Subscriber XiaoMingSub = new Observer.First.Subscriber("XiaoMing");
        Observer.First.QQGame qqGame1 = new Observer.First.QQGame("QQ Game", "Have a new game published ....");
        qqGame1.Subscriber = XiaoMingSub;
        qqGame1.Update();

        Console.ReadLine();
    }
}      

上面代碼确實實作了監控訂閱号的任務。但這裡的實作存在下面幾個問題:

  • QQGame類和Subscriber類之間形成了一種雙向依賴關系,一個類變化将引起另一個類的改變。
  • 當出現一個新的訂閱者時,此時不得不修改QQGame代碼,即添加另一個訂閱者的引用和在Update方法中調用另一個訂閱者的方法。

Next:

這樣的設計違背了“開放——封閉”原則,顯然不是我們想要的。下面做出改進:

  • 訂閱者抽象出一個接口,用它來取消QQGame類與具體的訂閱者之間的依賴
  • QQGame中采用一個清單來儲存所有的訂閱者對象,内部再添加對該清單的操作

實作代碼為:

namespace DesignPattern.Observer.Next
{
    public abstract class AbstractGame
    {
        // 訂閱者清單
        private List<IObserver> observers = new List<IObserver>();

        public string Symbol { get; set; }

        public string Info { get; set; }

        public AbstractGame(string symbol, string info)
        {
            this.Symbol = symbol;
            this.Info = info;
        }

        public void AddObserver(IObserver ob)
        {
            observers.Add(ob);
        }

        public void RemoveObserver(IObserver ob)
        {
            observers.Remove(ob);
        }

        public void Update()
        {
            foreach (var ob in observers)
            {
                if (ob != null)
                {
                    ob.ReceiveAndPrint(this);
                }
            }
        }
    }
}

namespace DesignPattern.Observer.Next
{
    public class QQGame : AbstractGame
    {
        public QQGame(string symbol, string info)
            : base(symbol, info)
        {
        }
    }
}

namespace DesignPattern.Observer.Next
{
    public interface IObserver
    {
        void ReceiveAndPrint(AbstractGame game);
    }
}

namespace DesignPattern.Observer.Next
{
    public class Subscriber : IObserver
    {
        public string Name { get; set; }

        public Subscriber(string name)
        {
            this.Name = name;
        }

        public void ReceiveAndPrint(AbstractGame game)
        {
            Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, game.Symbol, game.Info);
        }
    }
}

class Program
{
    /// <summary>
    /// 調用
    /// </summary>
    /// <param name="args"></param>
    static void Main(string[] args)
    {
        // 第二種方式
        Observer.Next.QQGame qqGame2 = new Observer.Next.QQGame("QQ Game", "Have a new game published ....");
        qqGame2.AddObserver(new Observer.Next.Subscriber("XiaoMing"));
        qqGame2.Update();

        Console.ReadLine();
    }
}      

這樣的實作就是觀察者模式的實作。在任何時候,隻要調用了AbstractGame類的Update方法,它就會通知所有的觀察者對象,同時,取消了直接依賴,變為間接依賴,這樣大大提供了系統的可維護性和可擴充性。

Last:

使用委托與事件來簡化觀察者模式的實作

namespace DesignPattern.Observer.Last
{
    public abstract class AbstractGame
    {
        // 委托充當訂閱者接口類
        public delegate void NotifyEventHandler(object sender);

        public NotifyEventHandler NotifyEvent;

        public string Symbol { get; set; }

        public string Info { get; set; }

        public AbstractGame(string symbol, string info)
        {
            this.Symbol = symbol;
            this.Info = info;
        }

        public void AddObserver(NotifyEventHandler ob)
        {
            NotifyEvent += ob;
        }

        public void RemoveObserver(NotifyEventHandler ob)
        {
            NotifyEvent -= ob;
        }

        public void Update()
        {
            if (NotifyEvent != null)
            {
                NotifyEvent(this);
            }
        }
    }
}

namespace DesignPattern.Observer.Last
{
    public class QQGame : AbstractGame
    {
        public QQGame(string symbol, string info)
            : base(symbol, info)
        {
        }
    }
}

namespace DesignPattern.Observer.Last
{
    public class Subscriber
    {
        public string Name { get; set; }

        public Subscriber(string name)
        {
            this.Name = name;
        }

        public void ReceiveAndPrint(Object obj)
        {
            AbstractGame game = obj as AbstractGame;
            if (game != null)
            {
                Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, game.Symbol, game.Info);
            }
        }
    }
}

class Program
{
    /// <summary>
    /// 調用
    /// </summary>
    /// <param name="args"></param>
    static void Main(string[] args)
    {
        // 第三種方式
        Observer.Last.QQGame qqGame3 = new Observer.Last.QQGame("QQ Game", "Have a new game published ....");
        var xiaoming = new Observer.Last.Subscriber("XiaoMing");
        var xiaohong = new Observer.Last.Subscriber("XiaoHong");

        qqGame3.AddObserver(new Observer.Last.AbstractGame.NotifyEventHandler(xiaoming.ReceiveAndPrint));
        qqGame3.AddObserver(new Observer.Last.AbstractGame.NotifyEventHandler(xiaohong.ReceiveAndPrint));

        qqGame3.Update();

        Console.ReadLine();
    }
}      

使用事件和委托實作的觀察者模式中,減少了訂閱者接口類的定義,此時,.委托正式充當訂閱者接口類的角色。

觀察者模式的優缺點

優點:

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

缺點:

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

觀察者模式的适用場景

以下情況可以考慮使用觀察者模式:

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

代碼

如需轉載,請在顯眼處标明本文連結,謝謝。