zoukankan      html  css  js  c++  java
  • 设计模式(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类图如下:

    代码实例

    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#的工程化实现及扩展》

  • 相关阅读:
    java 基础笔记 基本数据类型对象包装类
    java String 类 基础笔记
    java 线程 笔记 基础
    java 线程 基础笔记2
    java 异常学习 笔记
    广告简单概念整理-持续更新
    curl一些使用技巧
    简单学习正则表达式
    Linux命令简单操作之lsof
    Linux命令简单操作之find和xargs
  • 原文地址:https://www.cnblogs.com/zhixin9001/p/13583675.html
Copyright © 2011-2022 走看看