设计模式-观察者模式(Observer)

简介:

观察者模式,也称为订阅-发布模式,定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖他的对象都得到通知并被自动更新。

主要由以下几个部分组成:

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);
           }
       }
此时具体目标对象需要重写抽象目标对象的通知方法,在方法内部根据目标的属性来决定被通知的观察者。
 
 
posted @ 2013-10-24 21:51  liaozh  阅读(224)  评论(0编辑  收藏  举报