设计模式(20) 观察者模式

观察者模式是一种平时接触较多的模式。它主要用于一对多的通知发布机制,当一个对象发生改变时自动通知其他对象,其他对象便做出相应的反应,同时保证了被观察对象与观察对象之间没有直接的依赖。

GOF对观察者模式的描述为:
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

观察者模式的适用场景

  • 当存在一类对象通知关系上依赖于另一类对象的时候,把它们进行抽象,确保两类对象的具体实现都可以相对独立的变化,但它们交互的接口保持稳定。
  • 一个类型状态变化时,需要通知的对象的数量不固定,会有增加或删除若干被通知对象的情况。
  • 需要让目标对象与被通知对象之间保持松散耦合的时间。

UML类图如下:

20.observer.jpg

代码实例

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();//触发对外通知
    }
}

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;
    }
}

调用端

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 两个观察者都发生了变化
    subject -= observer2;
    subject.Update(40);
    Console.WriteLine($"ob1:{observer1.State}  ob2:{observer2.State}");
    //ob1:40 ob2:30 observer2被移除,不会跟随变化
}

这里的被观察者继承基类SubjectBase,观察者实现接口IObserver。SubjectBase和IObserver相互依赖,SubjectBase本身不知道会有哪些具体IObserver类型希望获得它的更新通知,具体的Observer类型也并不需要关心目标类型,只需要依赖SubjectcBase,所以实际上一个观察者可以跟踪多个被观察者。

推模式和拉模式

根据当目标对象状态更新的时候,观察者更新自己数据的方式,可以将观察者模式分为推模式和拉模式。

  • 推模式:目标对象在通知里把需要更新的信息作为参数提供给IObserver的Update()方法。采用这种方式,观察者只能只能被动接受,如果推送的内容比较多,那么对网络、内存或者I/O的开销就会很大。

  • 拉模式:目标对象仅仅告诉观察者有新的状态,至于该状态是什么,则需要观察者主动访问目标对象来获取。这种方式下,观察者获取信息的时机和内容都可以自主决定,但如果观察者没有及时获取信息,就会漏掉之前通知的内容。

前面的代码示例是两种方式的结合,看起来像是推模式,但他推送的是一个SubjectBase的引用,观察者可以根据需要通过这个引用访问到具体的状态,从这个角度看又是拉模式。

事件

.NET中的事件机制也可以看作观察者模式,事件所定义的委托类型本身就是个抽象的观察者,而且相对经典的观察者模式,事件更加简单、灵活,耦合也更加松散。
代码示例

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));
        }
    }
}

观察者,注册事件

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

观察者模式的缺点

  • 如果观察者比较多,逐个通知会相对耗时。
  • 测试和调试相比直接依赖更加困难。
  • 可能导致内存泄漏,即使所有观察者都已经失效了,但如果它们没有注销对主题对象的观察,那么观察者和主题对象间的这种相互的引用关系,会使双方无法被GC回收。

参考书籍:
王翔著 《设计模式——基于C#的工程化实现及扩展》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容