消息总线设计系列之 - 观察者模式
关于委托于事件的关系在网上已经到处可见了,尤其是子阳兄 的C#中的委托于事件更是将它的讲的深入浅出,通俗易懂,实在是博客中的精品之作,无论是初学者还是老江湖都可以从这篇文章中领略不少。本文将从观察者模式的角度进一步来探讨之,希望你先看一下子阳兄C#中的委托于事件 然后在看这篇文章,你的收获会是不一样的吆!(注:本文中的例子仍然选用子阳兄的例子,观察者模式的定义图例等大家可以在网上查阅,在这里就不一一细数了)
观察者委托的定义:
public delegate void ObserverDelegate(object e);
观察者IObserver:监听主题,如果主题的状态发生改变,就调用对应的回调函数Update做相应的处理
{
void Update(object e);
}
主题 Subject : 主题负责注册和移除观察者以及当主题数据状态发生改变时就给所注册的观察者
对象发送通知。
{
// 利用委托对象做为 观察者列表
private ObserverDelegate observerList;
//注册或移除观察者
public event ObserverDelegate Observers
{
add //注册观察者
{
observerList += value;
}
remove//移除观察者
{
observerList -= value;
}
}
protected virtual void OnHandler(object e)
{
if (observerList != null)
observerList (e);
}
//通知所有观察者 主题的状态发生改变
protected void Notify(object e)
{
OnHandler(e);
}
}
首先定义一个烧水的消息类,做为热水器的状态,具体代码如下:
{
public readonly Heater Heater;
public readonly int Temperature;
public BoiltWaterMessage(Heater heater, int temperature)
{
this.Heater = heater;
this.Temperature = temperature;
}
}
主题-热水器类:
class Heater : Subject
{
public string Type = "RealFire 001"; // 添加型号作为演示
public string Area = "China Xian"; // 添加产地作为演示
public void BoildWater()
{
for (int temperature = 0; temperature <= 100; temperature++)
{
if (temperature > 95)
{
//通知所有观察者 主题的状态发生改变
Notify(new BoiltWaterMessage(this, temperature));
}
}
}
}
观察者-警报器:
{
private static void MakeAlert(BoiltWaterMessage m)
{
Console.WriteLine("Alarm:{0} - {1}: ", m.Heater.Area, m.Heater.Type);
Console.WriteLine("Alarm: 嘀嘀嘀,水已经 {0} 度了:", m.Temperature.ToString());
Console.WriteLine();
}
public void Update(object e)
{
MakeAlert(e as BoiltWaterMessage);
}
}
观察者- 显示器
{
private static void ShowMsg(BoiltWaterMessage m)
{
Console.WriteLine("Display:{0} - {1}: ", m.Heater.Area, m.Heater.Type);
Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", m.Temperature.ToString());
Console.WriteLine();
}
public void Update(object e)
{
ShowMsg(e as BoiltWaterMessage);
}
}
测试代码:
Heater heater = new Heater();
//观察者 - 警报器
Alarm alerm = new Alarm();
//观察者 - 显示器
Display disp = new Display();
//注册观察者 - 警报器
heater.Observers += alerm.Update;
//注册观察者 - 显示器
heater.Observers += disp.Update;
heater.BoildWater();
//移除警报器观察者
heater.Observers -= alerm.Update;
heater.BoildWater();
//移除显示器观察者
heater.Observers -= disp.Update;
heater.BoildWater();
输出结果:
Alarm:China Xian - RealFire 001:
Alarm: 嘀嘀嘀,水已经 96 度了:
Display:China Xian - RealFire 001:
Display:水快烧开了,当前温度:96度。
Alarm:China Xian - RealFire 001:
Alarm: 嘀嘀嘀,水已经 97 度了:
Display:China Xian - RealFire 001:
Display:水快烧开了,当前温度:97度。
Alarm:China Xian - RealFire 001:
Alarm: 嘀嘀嘀,水已经 98 度了:
Display:China Xian - RealFire 001:
Display:水快烧开了,当前温度:98度。
Alarm:China Xian - RealFire 001:
Alarm: 嘀嘀嘀,水已经 99 度了:
Display:China Xian - RealFire 001:
Display:水快烧开了,当前温度:99度。
Alarm:China Xian - RealFire 001:
Alarm: 嘀嘀嘀,水已经 100 度了:
Display:China Xian - RealFire 001:
Display:水快烧开了,当前温度:100度。
Display:China Xian - RealFire 001:
Display:水快烧开了,当前温度:96度。
Display:China Xian - RealFire 001:
Display:水快烧开了,当前温度:97度。
Display:China Xian - RealFire 001:
Display:水快烧开了,当前温度:98度。
Display:China Xian - RealFire 001:
Display:水快烧开了,当前温度:99度。
Display:China Xian - RealFire 001:
Display:水快烧开了,当前温度:100度。
上面的代码是一个比较中规中矩的观察者,每一个观察者必须是一个实现了观察者接口的类,显然太繁琐了,当然在.NET 中完全可以利用委托的优势,用含有委托函数签名的类来代替。比如:
{
private void MakeAlert(object e)
{
BoiltWaterMessage m = e as BoiltWaterMessage;
Console.WriteLine("Alarm:{0} - {1}: ", m.Heater.Area, m.Heater.Type);
Console.WriteLine("Alarm: 嘀嘀嘀,水已经 {0} 度了:", m.Temperature.ToString());
Console.WriteLine();
}
private static void ShowMsg(object e)
{
BoiltWaterMessage m = e as BoiltWaterMessage;
Console.WriteLine("Display:{0} - {1}: ", m.Heater.Area, m.Heater.Type);
Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", m.Temperature.ToString());
Console.WriteLine();
}
public void Execute()
{
// 主题对象 - 热水器
Heater heater = new Heater();
//注册观察者 - 警报器
heater.Observers += MakeAlert;
//注册观察者 - 显示器
heater.Observers += ShowMsg;
heater.BoildWater();
//移除警报器观察者
heater.Observers -= MakeAlert;
heater.BoildWater();
//移除显示器观察者
heater.Observers -= ShowMsg;
heater.BoildWater();
Console.ReadLine();
}
}
人们对关灯和开灯的反映,其实也是一个观察者应用的典型例子。主题是灯,观察者是人,灯的状态(开/关)改变就会产生一个通知信号,该信号被人的大脑接收住之后,就产出了不同的反映。代码例子如下:
using System.Collections.Generic;
using System.Text;
namespace DelegateDemo
{
class Sample3:ICommand
{
class Lighter : Subject
{
private bool opened = false;
public bool Opened
{
get { return opened; }
set
{
if (opened != value)
{
opened = value;
Notify(value);
}
}
}
}
private void Response(string person,bool opened)
{
if (opened)
{
Console.WriteLine("{0} 激动地喊【奥,来电了!】", person);
}
else
{
Console.WriteLine("{0} 叹气的说【娘的,又停电了!】", person);
}
}
public void ZhangSan(object e)
{
Response("ZhangSan", (bool)e);
}
public void LiSi(object e)
{
Response("LiSi", (bool)e);
}
public void Execute()
{
Lighter lighter = new Lighter();
//张三进屋看到了一个大灯
lighter.Observers += ZhangSan;
//李四进屋看到了一个大灯
lighter.Observers += LiSi;
lighter.Opened = true;
lighter.Opened = false;
}
}
输出结果:
ZhangSan 激动地喊【奥,来电了!】
LiSi 激动地喊【奥,来电了!】
ZhangSan 叹气的说【娘的,又停电了!】
LiSi 叹气的说【娘的,又停电了!】
}
通过上面的例子可以看出观察者模式的骨架定义(观察者委托类型,以及主题类型)已经相对的通用了,但是里面有一些瑕疵,比如观察者委托类型的定义,虽然最通用了,但是回调函数处理时候必须要进行类型转化,太麻烦并且容易出错,有没有更好的办法,充分发挥强类型的优点?当然有了,那就是泛型委托,看下面相关的代码:
public delegate void ObserverDelegate<T>(T e);
//主题类
public class Subject<T>
{
// 利用委托对象做为 观察者列表
private ObserverDelegate<T> observerList;
//注册或移除观察者
public event ObserverDelegate<T> Observers
{
add //注册观察者
{
observerList += value;
}
remove//移除观察者
{
observerList -= value;
}
}
protected virtual void OnHandler(T e)
{
if (observerList != null)
observerList(e);
}
//通知所有观察者 主题的状态发生改变
protected void Notify(T e)
{
OnHandler(e);
}
}
利用新的骨架再把上面的两个例子进行串起来:
class Sample4:ICommand
{
class BoiltWaterMessage
{
public readonly Heater Heater;
public readonly int Temperature;
public BoiltWaterMessage(Heater heater, int temperature)
{
this.Heater = heater;
this.Temperature = temperature;
}
}
//泛型热水器类
class Heater : Subject<BoiltWaterMessage>
{
public string Type = "RealFire 001"; // 添加型号作为演示
public string Area = "China Xian"; // 添加产地作为演示
public void BoildWater()
{
for (int temperature = 0; temperature <= 100; temperature++)
{
if (temperature > 98)
{
Notify(new BoiltWaterMessage(this, temperature));
}
}
}
}
//现在的消息处理函数可是泛型的了呀,不需要类型转化了
private void MakeAlert(BoiltWaterMessage m)
{
Console.WriteLine("Alarm:{0} - {1}: ", m.Heater.Area, m.Heater.Type);
Console.WriteLine("Alarm: 嘀嘀嘀,水已经 {0} 度了:", m.Temperature.ToString());
Console.WriteLine();
}
//现在的消息处理函数可是泛型的了呀,不需要类型转化了
private static void ShowMsg(BoiltWaterMessage m)
{
Console.WriteLine("Display:{0} - {1}: ", m.Heater.Area, m.Heater.Type);
Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", m.Temperature.ToString());
Console.WriteLine();
}
//泛型灯泡,该回调函数的参数被BOOL类型限制住了
class Lighter : Subject<bool>
{
private bool opened = false;
public bool Opened
{
get { return opened; }
set
{
if (opened != value)
{
opened = value;
Notify(value);
}
}
}
}
private void Response(string person, bool opened)
{
if (opened)
{
Console.WriteLine("{0} 激动地喊【奥,来电了!】", person);
}
else
{
Console.WriteLine("{0} 叹气的说【娘的,又停电了!】", person);
}
}
//现在的消息处理函数可是泛型的了呀,不需要类型转化了
public void ZhangSan(bool e)
{
Response("ZhangSan", (bool)e);
}
//现在的消息处理函数可是泛型的了呀,不需要类型转化了
public void LiSi(bool e)
{
Response("LiSi", e);
}
public void Execute()
{
// 主题对象 - 热水器
Heater heater = new Heater();
//注册观察者 - 警报器
heater.Observers += MakeAlert;
//注册观察者 - 显示器
heater.Observers += ShowMsg;
heater.BoildWater();
Lighter lighter = new Lighter();
//张三进屋看到了一个大灯
lighter.Observers += ZhangSan;
//李四进屋看到了一个大灯
lighter.Observers += LiSi;
lighter.Opened = true;
lighter.Opened = false;
}
}
输出结果:
Alarm:China Xian - RealFire 001:
Alarm: 嘀嘀嘀,水已经 99 度了:
Display:China Xian - RealFire 001:
Display:水快烧开了,当前温度:99度。
Alarm:China Xian - RealFire 001:
Alarm: 嘀嘀嘀,水已经 100 度了:
Display:China Xian - RealFire 001:
Display:水快烧开了,当前温度:100度。
ZhangSan 激动地喊【奥,来电了!】
LiSi 激动地喊【奥,来电了!】
ZhangSan 叹气的说【娘的,又停电了!】
LiSi 叹气的说【娘的,又停电了!】
总结:本篇首先用一个弱类型并且中规中矩的观察者例子进行了展示,然后全用回调函数来代替一个观察者一个类的设计,最后又引出用泛型的观察者例子进行改进并收尾,希望这篇文章能给你带来一定的帮助,下一篇讲到中介者模式(调停者模式)时即可引出消息总线的影子了,最后附上代码