说起观察者模式,估计在园子里能搜出一堆来。所以写这篇博客的目的有两点:
1.观察者模式是写松耦合代码的必备模式,重要性不言而喻,抛开代码层面,许多组件都采用了Publish-Subscribe模式,所以我想按照自己的理解重新设计一个使用场景并把观察者模式灵活使用在其中
2.我想把C#中实现观察者模式的三个方案做一个总结,目前还没看到这样的总结
现在我们来假设这样的一个场景,并利用观察者模式实现需求:
未来智能家居进入了每家每户,每个家居都留有API供客户进行自定义整合,所以第一个智能闹钟(smartClock)先登场,厂家为此闹钟提供了一组API,当设置一个闹铃时间后该闹钟会在此时做出通知,我们的智能牛奶加热器,面包烘烤机,挤牙膏设备都要订阅此闹钟闹铃消息,自动为主人准备好牛奶,面包,牙膏等。
这个场景是很典型观察者模式,智能闹钟的闹铃是一个主题(subject),牛奶加热器,面包烘烤机,挤牙膏设备是观察者(observer),他们只需要订阅这个主题即可实现松耦合的编码模型。让我们通过三种方案逐一实现此需求。
一、利用.net的Event模型来实现
.net中的Event模型是一种典型的观察者模式,在.net出身之后被大量应用在了代码当中,我们看事件模型如何在此种场景下使用,
首先介绍下智能闹钟,厂家提供了一组很简单的API
public void SetAlarmTime(TimeSpan timeSpan)
{
_alarmTime = _now().Add(timeSpan);
RunBackgourndRunner(_now, _alarmTime);
}
</div>
SetAlarmTime(TimeSpan timeSpan)用来定时,当用户设置好一个时间后,闹钟会在后台跑一个类似于while(true)的循环对比时间,当闹铃时间到了后要发出一个通知事件出来
protected void RunBackgourndRunner(Func<DateTime> now,DateTime? alarmTime )
{
if (alarmTime.HasValue)
{
var cancelToken = new CancellationTokenSource();
var task = new Task(() =>
{
while (!cancelToken.IsCancellationRequested)
{
if (now.AreEquals(alarmTime.Value))
{
//闹铃时间到了
ItIsTimeToAlarm();
cancelToken.Cancel();
}
cancelToken.Token.WaitHandle.WaitOne(TimeSpan.FromSeconds(2));
}
}, cancelToken.Token, TaskCreationOptions.LongRunning);
task.Start();
}
}
</div>
其他代码并不重要,重点在当闹铃时间到了后要执行ItIsTimeToAlarm(); 我们在这里发出事件以便通知订阅者,.net中实现event模型有三要素,
1.为主题(subject)要定义一个event, public event Action<Clock, AlarmEventArgs> Alarm;
2.为主题(subject)的信息定义一个EventArgs,即AlarmEventArgs,这里面包含了事件所有的信息
3.主题(subject)通过以下方式发出事件
var args = new AlarmEventArgs(_alarmTime.Value, 0.92m);
OnAlarmEvent(args);
</div>
OnAlarmEvent方法的定义
public virtual void OnAlarm(AlarmEventArgs e)
{
if(Alarm!=null)
Alarm(this,e);
}
</div>
这里要注意命名,事件内容-AlarmEventArgs,事件-Alarm(动词,例如KeyPress),触发事件的方法 void OnAlarm(),这些元素都要符合事件模型的命名规范。
智能闹钟(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));
}
</div>
在面包烘烤机中同样可以用_clock.Alarm+=(clock,args)