智能闹钟(SmartClock)已经实现完毕,我们在牛奶加热器(MilkSchedule)中订阅这个Alarm消息:
public void PrepareMilkInTheMorning()
{
_clock.Alarm += (clock, args) =>
{
Message =
"Prepraring milk for the owner, The time is {0}, the electric quantity is {1}%".FormatWith(
args.AlarmTime, args.ElectricQuantity*100);
Console.WriteLine(Message);
};
_clock.SetAlarmTime(TimeSpan.FromSeconds(2));
}
  在面包烘烤机中同样可以用_clock.Alarm+=(clock,args)=>{//it is time to roast bread}订阅闹铃消息。
  至此,event模型介绍完毕,实现过程还是有点繁琐的,并且事件模型使用不当会有memory leak的问题,当观察者(obsever)订阅了一个生命周期较长的主题(该主题生命周期长于观察者),该观察者将不会被垃圾回收(因为还有引用指向主题),详见Understanding and Avoiding Memory Leaks with Event Handlers and Event Aggregators,开发者需要显示退订该主题(-=)。
  园子里老A也写过一篇如何利用弱引用解决该问题的博客:如何解决事件导致的Memory Leak问题:Weak Event Handlers。
  二、利用.net中IObservable<out T>和IObserver<in T>实现观察者模式
  IObservable<out T> 顾名思义-可观察的事物,即主题(subject),Observer很明显是观察者了。
  在我们的场景中智能闹钟是IObservable,该接口只定义了一个方法IDisposable Subscribe(IObserver<T> observer);该方法命名让人有点犯晕,Subscribe即订阅的意思,不同于之前提到过的观察者(observer)订阅主题(subject)。在这里是主题(subject)来订阅观察者(observer),其实这里也说得通,因为在该模型下,主题(subject)维护了一个观察者(observer)列表,所以有主题订阅观察者之说,我们来看闹钟的IDisposable Subscribe(IObserver<T> observer)实现:
  public IDisposable Subscribe(IObserver<AlarmData> observer)
  {
  if (!_observers.Contains(observer))
  {
  _observers.Add(observer);
  }
  return new DisposedAction(() => _observers.Remove(observer));
  }
  可以看到这里维护了一个观察者列表_observers,闹钟在到点了之后会遍历所有观察者列表将消息逐一通知给观察者
  public override void ItIsTimeToAlarm()
  {
  var alarm = new AlarmData(_alarmTime.Value, 0.92m);
  _observers.ForEach(o=>o.OnNext(alarm));
  }
  很明显,观察者有个OnNext方法,方法签名是一个AlarmData,代表了要通知的消息数据,接下来看看牛奶加热器的实现,牛奶加热器作为观察者(observer)当然要实现IObserver接口
public  void Subscribe(TimeSpan timeSpan)
{
_unSubscriber = _clock.Subscribe(this);
_clock.SetAlarmTime(timeSpan);
}
public  void Unsubscribe()
{
_unSubscriber.Dispose();
}
public void OnNext(AlarmData value)
{
Message =
"Prepraring milk for the owner, The time is {0}, the electric quantity is {1}%".FormatWith(
value.AlarmTime, value.ElectricQuantity * 100);
Console.WriteLine(Message);
}
  除此之外为了方便使用面包烘烤器,我们还加了两个方法Subscribe()和Unsubscribe(),看调用过程
  var milkSchedule = new MilkSchedule();
  //Act
  milkSchedule.Subscribe(TimeSpan.FromSeconds(12));
  三、Action函数式方案
  在介绍该方案之前我需要说明,该方案并不是一个观察者模型,但是它却可以实现同样的功能,并且使用起来更加简练,也是我喜欢的一种用法。
  这种方案中,智能闹钟(smartClock)提供的API需要设计成这样:
  public void SetAlarmTime(TimeSpan timeSpan,Action<AlarmData> alarmAction)
  {
  _alarmTime = _now().Add(timeSpan);
  _alarmAction = alarmAction;
  RunBackgourndRunner(_now, _alarmTime);
  }
  方法签名中要接受一个Action<T>,闹钟在到点后直接执行该Action<T>即可:
  public override void ItIsTimeToAlarm()
  {
  if (_alarmAction != null)
  {
  var alarmData = new AlarmData(_alarmTime.Value, 0.92m);
  _alarmAction(alarmData);
  }
  }
  牛奶加热器中使用这种API也很简单:
  _clock.SetAlarmTime(TimeSpan.FromSeconds(1), (data) =>
  {
  Message =
  "Prepraring milk for the owner, The time is {0}, the electric quantity is {1}%".FormatWith(
  data.AlarmTime, data.ElectricQuantity * 100);
  });
  在实际使用过程中我会把这种API设计成fluent api,调用起来代码更清晰:
  智能闹钟(smartClock)中的API:
  public Clock SetAlarmTime(TimeSpan timeSpan)
  {
  _alarmTime = _now().Add(timeSpan);
  RunBackgourndRunner(_now, _alarmTime);
  return this;
  }
  public void OnAlarm(Action<AlarmData> alarmAction)
  {
  _alarmAction = alarmAction;
  }
  牛奶加热器中进行调用:
  _clock.SetAlarmTime(TimeSpan.FromSeconds(2))
  .OnAlarm((data) =>
  {
  Message =
  "Prepraring milk for the owner, The time is {0}, the electric quantity is {1}%".FormatWith(
  data.AlarmTime, data.ElectricQuantity * 100);
  });
  显然改进后的写法语义更好:闹钟.设置闹铃时间().当报警时(()=>{执行以下功能})
  这种函数式写法更简练,但是也有明显的缺点,该模型不支持多个观察者,当面包烘烤机使用这样的API时,会覆盖牛奶加热器的函数,即每次只支持一个观察者使用。