前言
园子里有很多小牛大牛在分享这他们关于设计模式的认识,并详细的为大家讲述如何使用,我也从中受益匪浅。饮水思源,接下来我也将自己在学习和实践的过程中注意到的细节整理出来,一点点和大家分享。希望大家多拍砖~
下文中观察者模式的举例场景为:从前有“学校”,"公司"两个观察者,他们同时关注“天气”这个主题,当“天气”主题发布气象信息时“学校”和“公司”两个观察者都会做出自己响应。(ps:学校呢,下毛毛雨的时候照常做课间操,大雨就可以不出操了;公司呢,毛毛雨照常上班,大雨就通知放假了~~pss:就让我做个白日梦了)。但是有一次,突然挂起了飓风~~呼呼~~结果。。。。。。 预知结果如何,且听下文代码详解:
正文:当观察者模式遇到突如其来的飓风
故事发生在一个应用了观察者模式的小系统中。
小系统中的主题“天气”总是能在气象信息发生变化时及时通知它的两个粉丝(观察者)“学校”和“公司”。它们在诞生之时设计者为它们定义了松散耦合的结构,好让它们分别独立工作互不影响。下面我们先看一下它们的结构图吧:
具体的主题ConcreteSubject“ 天气”通过与观察者接口关联,找到两个具体的观察者对象“学校”和“公司”,在“天气”的消息Message发生变化时,会Notify它的观察者,通过接口NewMessage告诉它们,“注意注意气象信息Message变化啦”。有一天,“天气”主题像往常一样,通知观察者(学校和公司)要下“毛毛雨”,突然检测到有飓风来袭,于是赶紧又通知观察者“飓风”!!这时,意想不到的事情发生了,学校没见过飓风,乱了套;公司因为迟迟没有收到飓风通知,结果在公司认真干活的我们都被飓风卷到了夏威夷群岛了~~因此老板做了各顺水人情:夏威夷度假半个月....(ps:又在做白日梦了)。
C#的代码示例如下:
private static void TestObserverPattern()
{
try
{
ConcreteSubject subject_Weather = new ConcreteSubject("天气");
IObserver observer_School = new ConcreteObserver("学校");
IObserver observer_Company = new ConcreteObserver("公司");
subject_Weather.RegisterObserver(observer_School);
subject_Weather.RegisterObserver(observer_Company);
subject_Weather.Message = "毛毛雨";
subject_Weather.Notify();
Console.WriteLine();
subject_Weather.Message = "飓风";
subject_Weather.Notify();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
具体主题:ConcreteSubject
/// <summary>
/// 具体主题
/// </summary>
public class ConcreteSubject : ISubject
{
//主题观察者列表
private IList<IObserver> observers = new List<IObserver>();
//主题名称:如天气
public string SubjectName { get; set; }
//发布的消息
public string Message { get; set; }
//主题构造函数
public ConcreteSubject(string subjectName)
{
SubjectName = subjectName;
}
//注册观察者
public void RegisterObserver(IObserver o)
{
observers.Add(o);
}
//移除观察者
public void RemoveObserver(IObserver o)
{
observers.Remove(o);
}
//依次通知注册的观察者
public void Notify()
{
foreach (IObserver observer in observers)
{
observer.NewMessage(Message);
}
}
}
具体观察者:ConcreteObserver
/// <summary>
/// 具体观察者
/// </summary>
public class ConcreteObserver : IObserver
{
//观察者名称:如学校、公司
public string ObserverName { get; set; }
//观察者能识别的天气
string[] weather = { "毛毛雨", "大雨","晴天" };
//构造观察者对象
public ConcreteObserver(string observerName)
{
ObserverName = observerName;
}
//收到天气主题发来的通知
public void NewMessage(string msg)
{
if (weather.Contains(msg)) //观察者能识别的天气
{
Console.WriteLine(ObserverName + "收到新通知:" + msg);
}
else //突然来了鬼天气...
{
Console.WriteLine(ObserverName + "收到新通知:" + msg);
Console.WriteLine(ObserverName + "被吓坏了,没见过这鬼天气!!!乱套了...");
throw new Exception(ObserverName + "没处理好" + msg + ",出异常了");
}
}
}
故事的发生经过都控制台记录了下来,下面我们看一下控制台记录,然后从代码中分析,为什么飓风来了公司却没接到通知。控制台记录如下:
从控制台的记录可以看出,在“天气”主题发布毛毛雨消息时,学校和公司都依次接到了通知;但在飓风消息发布后,学校没见过这鬼天气,乱套了;结果是公司没收到这飓风预报,损失惨重(全体半月休假~_~)...
整个过程中可以看出,“天气”主题有不可推卸的责任:该通知的没通知到。检查一下“具体主题”天气的源代码,可以发现问题所在:
//依次通知注册的观察者
public void Notify()
{
foreach (IObserver observer in observers)
{
observer.NewMessage(Message);
}
}
在遍历注册的观察者列表依次通知观察者时,对实际的观察者接收消息方法产生了依赖。如果一个观察者接收到消息“乱了套”(像故事中遇到“飓风”的学校一样,出了异常),通知就会被中断,后果...各种严重...。
因此,在具体主题类在通知观察者时要加上异常处理,来增强系统的健壮性,从而保证每个观察者都能得到通知。
改进1.处理通知时异常(增强健壮性)
代码示例如下:
//依次通知注册的观察者
public void Notify()
{
foreach (IObserver observer in observers)
{
try
{
observer.NewMessage(Message);
}
catch (Exception ex) //某个观察者接收到消息后异常了
{
Console.WriteLine(ex.Message);//记录异常信息
continue; //继续通知下一观察者
}
}
}
故事中的“事故”是源于异常,在观察者模式中还有另一个可能会导致“事故”的是阻塞。处理了异常,可以保证每个观察者都能得到通知,但是通知是否及时是保证不了的,而通知不及时同样可能会给观察者带来“事故”。要解决类似这样的问题可以两个方向出发:
1. 具体主题对问题负全责:主题在通知注册观察者(各式各样的观察者 ps. 容易乱套的、喜欢拖延的等)时,通过异常处理保证健壮性(能通知到)、异步通知保证及时性。(异步通知可使用委托实现:异步调用示例讲解;简单示例代码见下文“改进2”)
2. 主题和观察者共同为问题负责:主题在通知注册观察者时,通过依次处理保证健壮性(能通知到),观察者负责在接收通知后不拖延保证及时性。
这要根据项目的实际情况来选择,如果你要开发“主题”类库对外发布订阅,观察者不再你的控制范围,那就从方向1出发,为自己的主题打造一个金刚不坏之神吧;如果你是主题的开发者同时也是主题的使用者,设计让主题和观察者协同工作也是一个便捷、高效的选择。
改进2.异步通知观察者(保证及时)
代码示例如下:
//定义通知委托
private delegate void NotifyHander(string msg);
//依次通知注册的观察者
public void Notify()
{
foreach (IObserver observer in observers)
{
try
{
//新建委托对象,包装观察者接收消息的方法
NotifyHandler handler=new NotifyHandler(observer.NewMessage);
//通过委托对象异步通知,只管发消息,不管结果如何~~
handler.BeginInvoke(Message,null,null);
}
catch (Exception ex) //某个观察者接收到消息后异常了
{
Console.WriteLine(ex.Message);//记录异常信息
continue; //继续通知下一观察者
}
}
}
另外,观察者设计模式的思想在委托(delegate)和事件(event)中有明显的体现。一个委托对象(或一个事件源)相当于一个主题,委托对象所引用的方法(可多个)相当于注册的观察者,当委托对象被调用时,注册在委托链上的方法会依次调用。同样的原理,如果委托引用的方法(各式各样的观察者)出现异常、阻塞,也可能会给委托链中的其他观察者带来危害。因此,在使用委托时请尊重委托(事件、主题)发布人对我们的信任,在+=后的方法中1.处理所有异常;2.不要做耗时的处理。
第1点是很容易做到的。对于第2点,如果确实有耗时的处理,先确认消息通知的频率和可能同时存在观察者的数量。如果频率高或可能同时存在观察者数量大,则可先将消息放入缓冲队列,另开辟线程完成耗时的处理;如果仅有自己一个观察者且消息通知频率低的可以有足够的事件完成耗时的处理,那就暂且保持现状吧。
本文中故事灵感来源与fejustin 《设计模式随笔系列:气象站的故事-观察者模式Observer》
博文链接 http://www.cnblogs.com/justinw/archive/2007/05/02/734522.html