简介:
观察者模式,也称为订阅-发布模式,定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖他的对象都得到通知并被自动更新。
主要由以下几个部分组成:
a.Subject目标对象。它具有以下特征:
一个目标可以被多个观察者订阅
提供订阅和取消订阅的方法
当目标对象状态发生变化时,通知所有订阅者。
把Subject独立出来是因为他提供了观察者模式中常见的三个特征,每个观察者模式都是这样,可以抽象出来。具体的单独逻辑可放在ConcreteSubject具体的目标实现对象中。
b.Observer定义观察者的接口。提供方法(一般为接口),当目标对象发生变化通知过来,做对应的响应操作。可以在该方法中调用Subject目标对象,以获取目标对象的数据。
c.ConcreteSubject具体的目标实现对象。可以维护具体对象的一些个性化属性,如目标状态等。
d.ConcreteObserver观察者的具体实现对象。提供处理目标对象变化的具体响应操作。
具体实例:
报社发行报纸,广大读者可以订报,也可以取消订报。报社维护订阅者的名单,一旦有新的报纸印刷出来,及时发布给广大读者。
Subject目标对象:提供观察者模式的基本功能
上面的例子中,广大读者都在观察同一个报社对象,这个报社对象就是被观察的目标。一般建议在目标接口名称后面加上Subject
/// <summary> /// Subject目标对象 /// </summary> public class NewsPaperSubject { /// <summary> /// 维护有效订阅者的列表。 /// </summary> protected List<ReaderObserver> observerList = new List<ReaderObserver>(); /// <summary> /// 提供公共接口,供注册订阅者 /// </summary> /// <param name="aObserver"></param> public void Attach(ReaderObserver aObserver) { observerList.Add(aObserver); } /// <summary> /// 提供公共接口,供取消注册订阅者 /// </summary> /// <param name="aObserver"></param> public void Detach(ReaderObserver aObserver) { observerList.Remove(aObserver); } /// <summary> /// 目标对象发生变化时,通知所有订阅的观察者 /// </summary> protected void NotifyAllObservers() { foreach (ReaderObserver observer in observerList) { observer.update(this); } } }
上面的例子中,仅实现了报社的基本功能,并没有定义报社出版报纸等功能,是为了这个更通用。具体的逻辑放在ConcreteSubject中。
ConcreteSubject具体的目标实现对象:真正的模板对象,日报
日报,在原来基类上新增属性:报纸的出版日期。修改了出版日期,相当于重新出版了新报纸,此时需要通知所有读者。
/// <summary> /// ConcreteSubject具体的目标实现对象 -日报/// </summary> public class DaliyNewsPaper : NewsPaperSubject { public string Date { get { return _date; } set { _date = value; //改了出版日期,相当于重新出版了新报纸,此时需要通知所有读者。 NotifyAllObservers(); } } private string _date; }
Observer定义观察者的接口:观察者的接口或者抽象类。
一般建议在观察者接口后面跟Observer
接口更新方法,建议命名以Update。
观察者订阅好报纸以后,在家里等着收报纸即可,没有其他的功能需要实现。这里先抽象出观察者接口,然后再后续类中具体实现。
/// <summary> /// Observer定义观察者的接口 /// </summary> public abstract class ReaderObserver { /// <summary> /// 目标对象发生变化通知过来,响应操作接口方法。 /// <param name="aSubject">推送过来的目标对象</param> /// </summary> public abstract void update(NewsPaperSubject aSubject); }
ConcreteSubject具体的目标实现对象:真正的观察者,读者
/// <summary> /// ConcreteObserver观察者的具体实现对象 -读者 /// </summary> public class DailyNewsPaperReader : ReaderObserver { public string Name { get { return _name; } set { _name = value; } } private string _name; public override void update(NewsPaperSubject aSubject) { Console.WriteLine("{0}已经收到{1}的报纸", _name, (aSubject as DaliyNewsPaper).Date); } }
测试代码如下:
public class ObserverTest { public static void Main(string[] args) { //创建目标对象:报纸 DaliyNewsPaper newsPaper = new DaliyNewsPaper(); //创建观察者对象:张三 DailyNewsPaperReader observer1 = new DailyNewsPaperReader(); observer1.Name = "张三"; //张三订报纸 newsPaper.Attach(observer1); //创建观察者对象:李四 DailyNewsPaperReader observer2 = new DailyNewsPaperReader(); observer2.Name = "李四"; //李四订报纸 newsPaper.Attach(observer2); //2013-10-24报纸出版,张三、李四收到报纸 Console.WriteLine("2013-10-24报纸出版了!!!"); newsPaper.Date = "2013-10-24"; Console.WriteLine(); //李四取消订报 newsPaper.Detach(observer2); //2013-10-25报纸出版,张三收到报纸 Console.WriteLine("2013-10-25报纸出版了!!!"); newsPaper.Date = "2013-10-25"; Console.ReadLine(); } }
补充分析:
1.观察者模式一般都是目标对象跟观察者一对多的关系,即一个目标有多个观察者。
对于观察者需要观察多个目标的情况下,可以修改观察者的UPDATE方法,根据传入参数决定是哪个目标的变化,或者干脆定义不同名字的UPDATE方法来对应不同的目标调用。
2.观察者模式的变例:区别对待观察者
比如说:某公司的请假制度为,请假半天以下的,通知下项目经理和项目组内成员即可,请假3天以下(含3天)的,还需要通知部门经理,请假3天以上的,还需要通知总经理。
实例中,请假对象是一个目标对象,项目经理、项目组成员、部门经理、总经理都是观察者。但是不同的是,并不是每次请假都需要通知上述所有人,需要根据请假的天数来决定通知的范围。
如果使用观察者模式?怎么处理?
参考代码如下:
protected void NotifyObservers() { foreach (ReaderObserver observer in observerList) { //不管请多少天,都需要通知项目经理和项目组成员 observer.getJob().equals("项目经理").Update(this); observer.getJob().equals("项目组成员").Update(this); if (this.Day > 0.5) observer.getJob().equals("部门经理").Update(this); if(this.Death >3) observer.getJob().equals("总经理").Update(this); } }
此时具体目标对象需要重写抽象目标对象的通知方法,在方法内部根据目标的属性来决定被通知的观察者。