Observer设计模式与C#委托、事件
最近新开发了一个内容服务,该服务用于统一管理所有需要静态化的内容,例如:最近需要把个人空间和博客统一成使用相同的模板(都换肤)、这样边栏上的小栏目需要有一个统一的访问点,并且这个访问点是需要静态化数据的,内容服务就是这样的一个访问点。内容服务的具体设计在以后的文章中会有介绍,但是现在先来解决主动更新中的一个问题。
先来看看内容服务的使用方是如何获取内容服务的数据的,调用方需要传入UserName(用户名)、ContentName(内容名称),然后调用后返回的是内容的详细内容。因为UserName和ContentName都可能是多样的,某用户可能会在更新多个不同的内容。在内容服务数据源的提供方的内部实现中,需要为不同的ContentName对变化进行订阅,也就是说当某个方法或者条件被触发了,就会更新订阅这个变化的所有ContentName对应的内容。显然,使用观察者模式就再适合不过了。以下是一下比较标准的Observer模式的实现(参考<<Head First 设计模式>>)。
观察者(Observer):
SampleContent01Observer 与 SampleContent02Observer 都实现了抽象ContentObserver,是两个不同的观察者。
ContentSubject 是监视对象,它实现了IContentSubject接口,只要实现该接口的类也都可以成为一个监视者。其中含有:
RegisterObserver方法,用于把观察者注册到监视对象中。
RemoveObserver方法,用于把已存在观察者从监对象中移除。
NotifyObservers方法,当监视对象的状态发生变化时,通知所有已注册的监视对象均会被通知。
所有监视对象都会具有这样的类拟特征。在这里要注意的是ContentSubject因为需要通知观察者,所以这里会产生一个依赖,当然依赖是一个抽象。我们平时在设计时最好养成依赖抽象而不是具体实现的习惯。
这里有它的使用方法:
其实,使用C#的委托与事件也能很好的设计出一个观察者模式,并且可以具有.NET的通用特性。如下就是一个使用委托与事件重写这样的逻辑的代码。
监视对象:
观察者:
标准的.Net Framework事件框架模型是有一定的编码规范的:
先来看看内容服务的使用方是如何获取内容服务的数据的,调用方需要传入UserName(用户名)、ContentName(内容名称),然后调用后返回的是内容的详细内容。因为UserName和ContentName都可能是多样的,某用户可能会在更新多个不同的内容。在内容服务数据源的提供方的内部实现中,需要为不同的ContentName对变化进行订阅,也就是说当某个方法或者条件被触发了,就会更新订阅这个变化的所有ContentName对应的内容。显然,使用观察者模式就再适合不过了。以下是一下比较标准的Observer模式的实现(参考<<Head First 设计模式>>)。
观察者(Observer):
public abstract class ContentObserver
{
private IContentSubject iContentSubject;
private ContentObserver() { }
public ContentObserver(IContentSubject iContentSubject)
{
this.iContentSubject = iContentSubject;
this.iContentSubject.RegisterObserver(this);//把自已注册为观察者
}
public virtual void SendContent(string userName)
{
if (String.IsNullOrEmpty(userName))
throw new ArgumentNullException("userName参数不能为空");
IContentSender iContentSender = ObjectFactory.CreateIContentSender();
iContentSender.SendContent(userName, this.ContentName);
}
public abstract ContentName ContentName { get; }
}
public class SampleContent01Observer : ContentObserver
{
public SampleContent01Observer(IContentSubject iContentSubject) : base(iContentSubject) { }
public override ContentName ContentName
{
get { return ContentName.SampleContent01; }
}
}
public class SampleContent02Observer : ContentObserver
{
public SampleContent02Observer(IContentSubject iContentSubject) : base(iContentSubject) { }
public override ContentName ContentName
{
get { return ContentName.SampleContent02; }
}
}
被观察的对象(Subject):{
private IContentSubject iContentSubject;
private ContentObserver() { }
public ContentObserver(IContentSubject iContentSubject)
{
this.iContentSubject = iContentSubject;
this.iContentSubject.RegisterObserver(this);//把自已注册为观察者
}
public virtual void SendContent(string userName)
{
if (String.IsNullOrEmpty(userName))
throw new ArgumentNullException("userName参数不能为空");
IContentSender iContentSender = ObjectFactory.CreateIContentSender();
iContentSender.SendContent(userName, this.ContentName);
}
public abstract ContentName ContentName { get; }
}
public class SampleContent01Observer : ContentObserver
{
public SampleContent01Observer(IContentSubject iContentSubject) : base(iContentSubject) { }
public override ContentName ContentName
{
get { return ContentName.SampleContent01; }
}
}
public class SampleContent02Observer : ContentObserver
{
public SampleContent02Observer(IContentSubject iContentSubject) : base(iContentSubject) { }
public override ContentName ContentName
{
get { return ContentName.SampleContent02; }
}
}
public interface IContentSubject
{
//注册一个观察者
void RegisterObserver(ContentObserver contentObserver);
//移除一个观察者
void RemoveObserver(ContentObserver contentObserver);
//状态改变时通知观察者
void NotifyObservers();
}
public class ContentSubject : IContentSubject
{
private List<ContentObserver> observers;
private string userName;
public ContentSubject()
{
observers = new List<ContentObserver>();
}
public void RegisterObserver(ContentObserver contentObserver)
{
observers.Add(contentObserver);
}
public void RemoveObserver(ContentObserver contentObserver)
{
int i = observers.IndexOf(contentObserver);
if (i >= 0)
observers.Remove(contentObserver);
}
public void NotifyObservers()
{
if (String.IsNullOrEmpty(userName))
throw new ArgumentNullException("userName参数不能为空");
foreach (ContentObserver contentObserver in observers)
{
contentObserver.SendContent(userName);
}
}
public void ContentChanged()
{
NotifyObservers();
}
public void SendContent(string userName)
{
this.userName = userName;
ContentChanged();
}
}
{
//注册一个观察者
void RegisterObserver(ContentObserver contentObserver);
//移除一个观察者
void RemoveObserver(ContentObserver contentObserver);
//状态改变时通知观察者
void NotifyObservers();
}
public class ContentSubject : IContentSubject
{
private List<ContentObserver> observers;
private string userName;
public ContentSubject()
{
observers = new List<ContentObserver>();
}
public void RegisterObserver(ContentObserver contentObserver)
{
observers.Add(contentObserver);
}
public void RemoveObserver(ContentObserver contentObserver)
{
int i = observers.IndexOf(contentObserver);
if (i >= 0)
observers.Remove(contentObserver);
}
public void NotifyObservers()
{
if (String.IsNullOrEmpty(userName))
throw new ArgumentNullException("userName参数不能为空");
foreach (ContentObserver contentObserver in observers)
{
contentObserver.SendContent(userName);
}
}
public void ContentChanged()
{
NotifyObservers();
}
public void SendContent(string userName)
{
this.userName = userName;
ContentChanged();
}
}
SampleContent01Observer 与 SampleContent02Observer 都实现了抽象ContentObserver,是两个不同的观察者。
ContentSubject 是监视对象,它实现了IContentSubject接口,只要实现该接口的类也都可以成为一个监视者。其中含有:
RegisterObserver方法,用于把观察者注册到监视对象中。
RemoveObserver方法,用于把已存在观察者从监对象中移除。
NotifyObservers方法,当监视对象的状态发生变化时,通知所有已注册的监视对象均会被通知。
所有监视对象都会具有这样的类拟特征。在这里要注意的是ContentSubject因为需要通知观察者,所以这里会产生一个依赖,当然依赖是一个抽象。我们平时在设计时最好养成依赖抽象而不是具体实现的习惯。
这里有它的使用方法:
ContentSubject contentSubject = new ContentSubject();
contentSubject.RegisterObserver(new SampleContent01Observer(contentSubject));
contentSubject.RegisterObserver(new SampleContent02Observer(contentSubject));
contentSubject.SendContent("billchen01");
contentSubject.SendContent("billchen02");
contentSubject.RegisterObserver(new SampleContent01Observer(contentSubject));
contentSubject.RegisterObserver(new SampleContent02Observer(contentSubject));
contentSubject.SendContent("billchen01");
contentSubject.SendContent("billchen02");
其实,使用C#的委托与事件也能很好的设计出一个观察者模式,并且可以具有.NET的通用特性。如下就是一个使用委托与事件重写这样的逻辑的代码。
监视对象:
/// <summary>
/// 内容更新触发器
/// </summary>
public class ContentTrigger
{
public bool IsUpdate { get; private set; }//指示是否需要更新
public delegate void ContentChangedEventHandler(Object sender, ContentChangedEventArgs e);//声明委托
public event ContentChangedEventHandler ContentChanged;//声明事件
//定义ContentChangedEventArgs类,传递给Observer所感兴趣的信息
public class ContentChangedEventArgs : EventArgs
{
public readonly string UserName;
public ContentChangedEventArgs(string userName)
{
this.UserName = userName;
}
}
protected virtual void OnContentChanged(ContentChangedEventArgs e)
{
if (ContentChanged != null)//如果有对象注册
{
ContentChanged(this, e);//调用所有注册对象的方法
}
}
public void UpdateContent(string userName)
{
if (!String.IsNullOrEmpty(userName))
{
this.IsUpdate = true;//标记为可更新
ContentChangedEventArgs e = new ContentChangedEventArgs(userName);//建立ContentChangedEventArgs对象
OnContentChanged(e);//调用OnContentChanged方法
}
}
}
/// 内容更新触发器
/// </summary>
public class ContentTrigger
{
public bool IsUpdate { get; private set; }//指示是否需要更新
public delegate void ContentChangedEventHandler(Object sender, ContentChangedEventArgs e);//声明委托
public event ContentChangedEventHandler ContentChanged;//声明事件
//定义ContentChangedEventArgs类,传递给Observer所感兴趣的信息
public class ContentChangedEventArgs : EventArgs
{
public readonly string UserName;
public ContentChangedEventArgs(string userName)
{
this.UserName = userName;
}
}
protected virtual void OnContentChanged(ContentChangedEventArgs e)
{
if (ContentChanged != null)//如果有对象注册
{
ContentChanged(this, e);//调用所有注册对象的方法
}
}
public void UpdateContent(string userName)
{
if (!String.IsNullOrEmpty(userName))
{
this.IsUpdate = true;//标记为可更新
ContentChangedEventArgs e = new ContentChangedEventArgs(userName);//建立ContentChangedEventArgs对象
OnContentChanged(e);//调用OnContentChanged方法
}
}
}
观察者:
public class ContentObservers
{
//公共的发送内容的方法
private static void SendContent(string userName, ContentName contentName)
{
IContentSender iContentSender = ObjectFactory.CreateIContentSender();
iContentSender.SendContent(userName, contentName);
}
public static void SampleContent01(Object sender, ContentTrigger.ContentChangedEventArgs e)
{
ContentTrigger trigger = (ContentTrigger)sender;
if (trigger.IsUpdate && !String.IsNullOrEmpty(e.UserName))
{
SendContent(e.UserName, ContentName.SampleContent01);
}
}
public static void SampleContent02(Object sender, ContentTrigger.ContentChangedEventArgs e)
{
ContentTrigger trigger = (ContentTrigger)sender;
if (trigger.IsUpdate && !String.IsNullOrEmpty(e.UserName))
{
SendContent(e.UserName, ContentName.SampleContent02);
}
}
}
{
//公共的发送内容的方法
private static void SendContent(string userName, ContentName contentName)
{
IContentSender iContentSender = ObjectFactory.CreateIContentSender();
iContentSender.SendContent(userName, contentName);
}
public static void SampleContent01(Object sender, ContentTrigger.ContentChangedEventArgs e)
{
ContentTrigger trigger = (ContentTrigger)sender;
if (trigger.IsUpdate && !String.IsNullOrEmpty(e.UserName))
{
SendContent(e.UserName, ContentName.SampleContent01);
}
}
public static void SampleContent02(Object sender, ContentTrigger.ContentChangedEventArgs e)
{
ContentTrigger trigger = (ContentTrigger)sender;
if (trigger.IsUpdate && !String.IsNullOrEmpty(e.UserName))
{
SendContent(e.UserName, ContentName.SampleContent02);
}
}
}
标准的.Net Framework事件框架模型是有一定的编码规范的:
- 委托类型的名称都应该以EventHandler结束。
- 委托类型的名称都应该以EventHandler结束。
- 委托的原型定义:有一个void返回值,并接受两个输入参数:一个Object 类型,一个 EventArgs类型(或继承自EventArgs)。
- 事件的命名为 委托去掉 EventHandler之后剩余的部分。
- 继承自EventArgs的类型应该以EventArgs结尾。
public delegate void ContentChangedEventHandler(Object sender, ContentChangedEventArgs e);的方法签名中,Object sender表示监视对象(ContentTrigger),而ContentChangedEventArgs e包含了Observer所感兴趣的状态(通过参数形式传递)。
ContentChangedEventArgs 参数必须继承于EventArgs,其中可以定义Subject需要传递给Observer的参数
public class ContentChangedEventArgs : EventArgs
{
public readonly string UserName;
public ContentChangedEventArgs(string userName)
{
this.UserName = userName;
}
}
{
public readonly string UserName;
public ContentChangedEventArgs(string userName)
{
this.UserName = userName;
}
}
现在改用了事件模型后,监视对象就没有直接依赖于观察者了,要求观察者的方法具有与委托相同的签名,并且它们现在都依赖于EventArgs了,这样的设计比上个例子更具有灵活性,而且于.net的事件调用模型是一样的,比较容易让人接受。以下是调用例子:
protected void Page_Load(object sender, EventArgs e)
{
ContentTrigger trigger = new ContentTrigger();
trigger.ContentChanged += new ContentTrigger.ContentChangedEventHandler(trigger_ContentChanged);
trigger.ContentChanged += ContentObservers.SampleContent01;
trigger.ContentChanged += ContentObservers.SampleContent02;
trigger.UpdateContent("billchen01");
trigger.UpdateContent("billchen02");
}
private void trigger_ContentChanged(object sender, ContentTrigger.ContentChangedEventArgs e)
{
ContentTrigger trigger = (ContentTrigger)sender;
Response.Output.Write(String.Format("{0} : {1}<br/>", trigger.IsUpdate, e.UserName));
}
{
ContentTrigger trigger = new ContentTrigger();
trigger.ContentChanged += new ContentTrigger.ContentChangedEventHandler(trigger_ContentChanged);
trigger.ContentChanged += ContentObservers.SampleContent01;
trigger.ContentChanged += ContentObservers.SampleContent02;
trigger.UpdateContent("billchen01");
trigger.UpdateContent("billchen02");
}
private void trigger_ContentChanged(object sender, ContentTrigger.ContentChangedEventArgs e)
{
ContentTrigger trigger = (ContentTrigger)sender;
Response.Output.Write(String.Format("{0} : {1}<br/>", trigger.IsUpdate, e.UserName));
}