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压力。