行为型模式-观察者模式的实现(C#)

1. 定义

Define a one-to-many dependency between objects so that when oneobject changes state, all its dependents are notified and updatedautomatically..

— Design Patterns : Elements of Reusable Object-Oriented Software
观察者模式(Observer Pattern),又称为发布/订阅模式,它是软件设计模式中的一种。观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

在观察者模式中,一个目标物件(被观察者)管理所有依赖于他的观察者,并且在它本身的状态发生改变时主动发出通知,这通常通过呼叫各个观察者所提供的方法来实现。

这种模式通常被用来实现事件处理系统。

观察者模式有很多实现方式,从根本上说,该模式必须包含两个角色:观察者和被观察者。

观察者和被观察者之间的互动关系不能是类之间的直接调用,那样就将观察者和被观察对象紧密耦合起来了,从而违反了面向对象设计原则。

1.1 建模

经典观察者模式类图如下,在这个模型中,抽象的被被观察者(主题ISubject)有注册(attach)、取消注册(detech)、通知(notify)方法。
由于所有观察者均抽象成了IObserver,从而解除了主题与具体观察者之间的耦合。

2.C# 观察者的实现模式

在事件处理场景,很自然会想到利用event 关键字 和EventHandler来实现观察者模式,这是一种最简单的方法。
同时C#也提供了一种低级抽象IObserver--IObservable(System命名空间,来自System.Runtime.dll) 来实现观察者。
被观察者(主题)接口:

namespace System
{
    public interface IObservable<out T>
    {       
        IDisposable Subscribe(IObserver<T> observer);
    }
}

观察者接口:

namespace System
{
   
    public interface IObserver<in T>
    {
        void OnCompleted();
        void OnError(Exception error);
        void OnNext(T value);
    }
}

上述接口中,由被观察者将观察者注册到观察者列表,观察者分别有完成、失败、执行的动作。

3. 案例

本文将基于一个经典案例分别用event 和IObserver 实现观察者模式。
场景描述:
气象部门根据气象卫星获取温度信息,当温度超过某一阈值时,需要向各单位发出高温预警通知,以便其及时做好高温防护错误。

在这个场景中,发布者是预警系统,观察者是各个单位。

抽象的模型图如下:

3.1 基于event 实现 观察者

发布者(主题)的定义:

public class Subject : IObservable<decimal>
{
        /// <summary>
        /// 观察者处理委托
        /// </summary>
        public event EventHandler<decimal> Observers;

        /*
         * 高温黄色预警 >=35,<37 
         * 高温橙色预警 >=37,<40 
         * 高温红色预警 >=40    
         * **/
        public void SetTemperature(decimal temperature)
        {

            if (temperature >= 35)
            {
                if (temperature >= 40)
                {
                    _warningLevel = "红色";
                }
                else if (temperature >= 37)
                {
                    _warningLevel = "橙色";
                }

                PublishWarning(temperature, _warningLevel);
            }
        }
        
        private void PublishWarning(decimal temperature, string warningLevel)
        {
            Console.WriteLine($"===========气象部门发布高温{_warningLevel} 预警,气温:{temperature} ");
            
            Observers?.Invoke(this, temperature);
        }
      
}

分别定义三个观察者

    /// <summary>
    /// 企业单位观察者
    /// </summary>
  public class EnterpriseObserver 
  {
        public void OnWarning(object sender,decimal eventArgs)
        {
            Console.WriteLine($"企业单位收到预警事件,气温:{eventArgs}");
        }
  }

    /// <summary>
    /// 政府部门观察者
    /// </summary>
  public class EnterpriseObserver 
  {
        public void OnWarning(object sender, decimal eventArgs)
        {
            Console.WriteLine($"政府部门收到预警事件,气温:{eventArgs}");
        }
  }

    /// <summary>
    /// 个人观察者
    /// </summary>
  public class EnterpriseObserver 
  {
        public void OnWarning(object sender, decimal eventArgs)
        {
            Console.WriteLine($"个人收到预警事件,气温:{eventArgs}");
        }
  }

客户端调用,利用委托可以多播的特性增加多个观察者:

            var subject = new Subject();

            subject.Observers += new EnterpriseObserver().OnWarning;
            subject.Observers += new GovernmentObserver().OnWarning;
            subject.Observers += new PersonObserver().OnWarning;
            int t = 3;
            var random = new Random(500);
            while (t > 0)
            {

                var temperature = random.NextDouble() * 50;
                if (temperature > 35)
                {
                    subject.SetTemperature((decimal)temperature);

                    t--;
                }
            }
  • 调用结果

3.2 基于IObserver--IObservable 实现观察者

在上面的代码中进行改造
发布者(主题)的定义:

   public class Unsubscriber<T> : IDisposable
    {
        private List<IObserver<T>> _observers;
        private IObserver<T> _observer;
        public Unsubscriber(List<IObserver<T>> observers,
            IObserver<T> observer)
        {
            _observers = observers;
            _observer = observer;
        }
        public void Dispose()
        {
            Console.WriteLine("Unsubscribed....");
            _observers.Remove(_observer);
        }
    }

    /// <summary>
    /// 发布者
    /// </summary>
    public class Subject : IObservable<decimal>
    {
        /// <summary>
        /// 观察者列表
        /// </summary>
        private List<IObserver<decimal>> observers;

        /// <summary>
        /// 观察者处理委托
        /// </summary>
        public event EventHandler<decimal> Observers;

        private decimal _temperature;
        private string _warningLevel;


        public Subject()
        {
            observers = new List<IObserver<decimal>>();
        }

        public IDisposable Subscribe(IObserver<decimal> observer)
        {
            if (!observers.Contains(observer))
                observers.Add(observer);
            return new Unsubscriber<decimal>(observers, observer);
        }


        /*
         * 高温黄色预警 >=35,<37 C
         * 高温橙色预警 >=37,<40 C
         * 高温红色预警 >=40    C
         * **/
        public void SetTemperature(decimal temperature)
        {

            if (temperature >= 35)
            {
                if (temperature >= 40)
                {
                    _warningLevel = "红色";
                }
                else if (temperature >= 37)
                {
                    _warningLevel = "橙色";
                }

                PublishWarning(temperature, _warningLevel);
            }
        }

        private void PublishWarning(decimal temperature, string warningLevel)
        {
            Console.WriteLine($"===========气象部门发布高温{_warningLevel} 预警,气温:{temperature} ");
            foreach (var observer in observers)
            {
                observer.OnNext(temperature);
                
            }
            Observers?.Invoke(this, temperature);
        }
    }

观察者定义:

 /// <summary>
    /// 企业单位观察者
    /// </summary>
    public class EnterpriseObserver : IObserver<decimal>
    {
        public void OnCompleted()
        {
        }

        public void OnError(Exception error)
        {
        }

        public void OnNext(decimal value)
        {
            Console.WriteLine($"企业单位收到预警信息,气温:{value}");
        }

        public void OnWarning(object sender,decimal eventArgs)
        {
            Console.WriteLine($"企业单位收到预警事件,气温:{eventArgs}");
        }
    }

    /// <summary>
    /// 政府部门
    /// </summary>
    public class GovernmentObserver : IObserver<decimal>
    {
        public void OnCompleted()
        {
        }

        public void OnError(Exception error)
        {
        }

        public void OnNext(decimal value)
        {
            Console.WriteLine($"政府部门收到预警信息,气温:{value}");
        }
        public void OnWarning(object sender, decimal eventArgs)
        {
            Console.WriteLine($"政府部门收到预警事件,气温:{eventArgs}");
        }
    }

    /// <summary>
    /// 个人观察者
    /// </summary>
    public class PersonObserver : IObserver<decimal>
    {
        public void OnCompleted()
        {
        }

        public void OnError(Exception error)
        {
        }

        public void OnNext(decimal value)
        {
            Console.WriteLine($"个人收到预警信息,气温:{value}");
        }

        public void OnWarning(object sender, decimal eventArgs)
        {
            Console.WriteLine($"个人收到预警事件,气温:{eventArgs}");
        }
    }

客户端调用:

var subject = new Subject();

            var ob1 = new EnterpriseObserver();
            var ob2 = new GovernmentObserver();
            var ob3 = new PersonObserver();

            subject.Subscribe(ob1);
            subject.Subscribe(ob2);
            subject.Subscribe(ob3);
            int t = 3;
            var random = new Random(500);
            while (t > 0)
            {
               
                var temperature = random.NextDouble()*50;
                if (temperature > 35)
                {
                    subject.SetTemperature((decimal)temperature);

                    t--;
                }
            }

4. 小结

  • 用C#实现观察者模式时,基本不需要自己定义发布者和观察者的抽象,基础类库中已经提供了现成的。
  • 一般情况下,使用event 编程模型实现观察者模式更加精简,编码少。
  • 仅在一些对性能要求比较高的特殊场景,我们可以直接使用IObserver-IObservable接口实现观察者模式,以减少event实例化的开销,减轻GC压力。
posted @ 2021-02-19 15:46  aimigi  阅读(312)  评论(0编辑  收藏  举报