三种观察者模式的C#实现
作者:网络转载 发布时间:[ 2015/4/2 11:09:36 ] 推荐标签:C# 软件开发 模式
智能闹钟(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时,会覆盖牛奶加热器的函数,即每次只支持一个观察者使用。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系SPASVO小编(021-61079698-8054),我们将立即处理,马上删除。
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11热门文章
常见的移动App Bug??崩溃的测试用例设计如何用Jmeter做压力测试QC使用说明APP压力测试入门教程移动app测试中的主要问题jenkins+testng+ant+webdriver持续集成测试使用JMeter进行HTTP负载测试Selenium 2.0 WebDriver 使用指南