設(shè)計(jì)模式(20) 觀察者模式

觀察者模式是一種平時(shí)接觸較多的模式。它主要用于一對(duì)多的通知發(fā)布機(jī)制,當(dāng)一個(gè)對(duì)象發(fā)生改變時(shí)自動(dòng)通知其他對(duì)象,其他對(duì)象便做出相應(yīng)的反應(yīng),同時(shí)保證了被觀察對(duì)象與觀察對(duì)象之間沒有直接的依賴。

GOF對(duì)觀察者模式的描述為:
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically..
— Design Patterns : Elements of Reusable Object-Oriented Software

觀察者模式的適用場景

  • 當(dāng)存在一類對(duì)象通知關(guān)系上依賴于另一類對(duì)象的時(shí)候,把它們進(jìn)行抽象,確保兩類對(duì)象的具體實(shí)現(xiàn)都可以相對(duì)獨(dú)立的變化,但它們交互的接口保持穩(wěn)定。
  • 一個(gè)類型狀態(tài)變化時(shí),需要通知的對(duì)象的數(shù)量不固定,會(huì)有增加或刪除若干被通知對(duì)象的情況。
  • 需要讓目標(biāo)對(duì)象與被通知對(duì)象之間保持松散耦合的時(shí)間。

UML類圖如下:

20.observer.jpg

代碼實(shí)例

public interface IObserver<T>
{
    void Update(SubjectBase<T> subject);
}

public abstract class SubjectBase<T>
{
    protected IList<IObserver<T>> observers = new List<IObserver<T>>();

    protected T state;
    public virtual T State
    {
        get { return state; }
    }

    //Attach
    public static SubjectBase<T> operator +(SubjectBase<T> subject, IObserver<T> observer)
    {
        subject.observers.Add(observer);
        return subject;
    }

    //Detach
    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();//觸發(fā)對(duì)外通知
    }
}

public class Subject<T> : SubjectBase<T> { }

public class Observer<T> : IObserver<T>
{
    public T State;
    public void Update(SubjectBase<T> subject)
    {
        this.State = subject.State;
    }
}

調(diào)用端

static void Main(string[] args)
{
    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(30);
    Console.WriteLine($"ob1:{observer1.State}  ob2:{observer2.State}");
    //ob1:30 ob2:30 兩個(gè)觀察者都發(fā)生了變化
    subject -= observer2;
    subject.Update(40);
    Console.WriteLine($"ob1:{observer1.State}  ob2:{observer2.State}");
    //ob1:40 ob2:30 observer2被移除,不會(huì)跟隨變化
}

這里的被觀察者繼承基類SubjectBase,觀察者實(shí)現(xiàn)接口IObserver。SubjectBase和IObserver相互依賴,SubjectBase本身不知道會(huì)有哪些具體IObserver類型希望獲得它的更新通知,具體的Observer類型也并不需要關(guān)心目標(biāo)類型,只需要依賴SubjectcBase,所以實(shí)際上一個(gè)觀察者可以跟蹤多個(gè)被觀察者。

推模式和拉模式

根據(jù)當(dāng)目標(biāo)對(duì)象狀態(tài)更新的時(shí)候,觀察者更新自己數(shù)據(jù)的方式,可以將觀察者模式分為推模式和拉模式。

  • 推模式:目標(biāo)對(duì)象在通知里把需要更新的信息作為參數(shù)提供給IObserver的Update()方法。采用這種方式,觀察者只能只能被動(dòng)接受,如果推送的內(nèi)容比較多,那么對(duì)網(wǎng)絡(luò)、內(nèi)存或者I/O的開銷就會(huì)很大。

  • 拉模式:目標(biāo)對(duì)象僅僅告訴觀察者有新的狀態(tài),至于該狀態(tài)是什么,則需要觀察者主動(dòng)訪問目標(biāo)對(duì)象來獲取。這種方式下,觀察者獲取信息的時(shí)機(jī)和內(nèi)容都可以自主決定,但如果觀察者沒有及時(shí)獲取信息,就會(huì)漏掉之前通知的內(nèi)容。

前面的代碼示例是兩種方式的結(jié)合,看起來像是推模式,但他推送的是一個(gè)SubjectBase的引用,觀察者可以根據(jù)需要通過這個(gè)引用訪問到具體的狀態(tài),從這個(gè)角度看又是拉模式。

事件

.NET中的事件機(jī)制也可以看作觀察者模式,事件所定義的委托類型本身就是個(gè)抽象的觀察者,而且相對(duì)經(jīng)典的觀察者模式,事件更加簡單、靈活,耦合也更加松散。
代碼示例

public class UserEventArgs : EventArgs
{
    public string Name { get; }
    public UserEventArgs(string name)
    {
        this.Name = name;
    }
}

public class User
{
    public event EventHandler<UserEventArgs> NameChanged;
    private string name;
    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            NameChanged(this, new UserEventArgs(value));
        }
    }
}

觀察者,注冊(cè)事件

public class Test
{
    public static void Entry()
    {
        User user = new User();
        user.NameChanged += (sender, args) =>
        {
            Console.WriteLine(args.Name);
        };
        user.Name = "Andy";
    }
}

觀察者模式的缺點(diǎn)

  • 如果觀察者比較多,逐個(gè)通知會(huì)相對(duì)耗時(shí)。
  • 測試和調(diào)試相比直接依賴更加困難。
  • 可能導(dǎo)致內(nèi)存泄漏,即使所有觀察者都已經(jīng)失效了,但如果它們沒有注銷對(duì)主題對(duì)象的觀察,那么觀察者和主題對(duì)象間的這種相互的引用關(guān)系,會(huì)使雙方無法被GC回收。

參考書籍:
王翔著 《設(shè)計(jì)模式——基于C#的工程化實(shí)現(xiàn)及擴(kuò)展》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容