设计模式之观察者模式

来源:互联网 发布:人族女捏脸数据导入图 编辑:程序博客网 时间:2024/05/22 02:13

1. 概述

  有时被称作发布/订阅模式,观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

2. 解决的问题

  将一个系统分割成一个一些类相互协作的类有一个不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。观察者就是解决这类的耦合关系的。

3. 模式中的角色

  1)抽象主题(Subject):它把所有观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。

  2)具体主题(ConcreteSubject):将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。

  3)抽象观察者(Observer):为所有的具体观察者定义一个接口,在得到主题通知时更新自己。

  4)具体观察者(ConcreteObserver):实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题状态协调。

4. 模式解读

  1)观察者模式的类图  

  

  2)观察者模式的代码

/// <summary>    /// 抽象主题类    /// </summary>    public abstract class Subject    {        private IList<Observer> observers = new List<Observer>();  //声明聚集        /// <summary>        /// 增加观察者        /// </summary>        /// <param name="observer"></param>        public void Attach(Observer observer)        {            observers.Add(observer);        }        /// <summary>        /// 移除观察者        /// </summary>        /// <param name="observer"></param>        public void Detach(Observer observer)        {            observers.Remove(observer);        }        /// <summary>        /// 向观察者(们)发出通知        /// </summary>        public void Notify()        {            foreach (Observer o in observers)            {                o.Update();            }        }    }    /// <summary>    /// 抽象观察者类,为所有具体观察者定义一个接口,在得到通知时更新自己    /// </summary>    public abstract class Observer    {        public abstract void Update();    }    /// <summary>    /// 具体观察者或具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。    /// </summary>    public class ConcreteSubject : Subject    {        private string subjectState;        /// <summary>        /// 具体观察者的状态        /// </summary>        public string SubjectState        {            get { return subjectState; }            set { subjectState = value; }        }    }    /// <summary>    /// 具体观察者,实现抽象观察者角色所要求的更新接口,已是本身状态与主题状态相协调    /// </summary>    public class ConcreteObserver : Observer    {        private string observerState;        private string name;        private ConcreteSubject subject;        /// <summary>        /// 具体观察者用一个具体主题来实现        /// </summary>        public ConcreteSubject Subject        {            get { return subject; }            set { subject = value; }        }        public ConcreteObserver(ConcreteSubject subject, string name)        {            this.subject = subject;            this.name = name;        }        /// <summary>        /// 实现抽象观察者中的更新操作        /// </summary>        public override void Update()        {            observerState = subject.SubjectState;            Console.WriteLine("The observer's state of {0} is {1}", name, observerState);        }    }


      3)客户端代码


class Program    {        static void Main(string[] args)        {            // 具体主题角色通常用具体自来来实现            ConcreteSubject subject = new ConcreteSubject();            subject.Attach(new ConcreteObserver(subject, "Observer A"));            subject.Attach(new ConcreteObserver(subject, "Observer B"));            subject.Attach(new ConcreteObserver(subject, "Observer C"));            subject.SubjectState = "Ready";            subject.Notify();            Console.Read();        }    }


运行结果

  

5. 模式总结

  1)优点

  观察者模式解除了主题和具体观察者的耦合,让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。

  2)缺点

  依赖关系并未完全解除,抽象通知者依旧依赖抽象的观察者。

  3)适用场景:最典型的是GUI应用程序的设计;

  当一个对象的改变需要给变其它对象时,而且它不知道具体有多少个对象有待改变时。

  一个抽象某型有两个方面,当其中一个方面依赖于另一个方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。

6. 观察者模式中客户端是如何知道它所依赖的对象信息发生改变了的呢?

 1)客户端(观察者)的实现类必须注册它们感兴趣的对象(被观察者),并且自身能做出相应变更;

 2)当被观察者的实现类的值发生变化时,必须记得通知管程者;

7. 模式引申,应用C#中的事件委托来彻底解除通知者和观察者之间的耦合。

   1)关于委托的定义:委托是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法有相同的行为。委托方法可以像其它任何方法一样,具有参数和返回值。委托可以看作是对函数(方法)的的抽象,是函数的“类”,委托的实例代表一个(或多个)具体的函数,它可以是多播的。

   2)关于事件:事件基于委托,为委托提供了一种发布/订阅机制。事件的订阅与取消与我们刚才讲的观察者模式中的订阅与取消类似,只是表现形式有所不同。在观察者模式中,订阅使用方法Attach()来进行;在事件的订阅中使用“+=”。类似地,取消订阅在观察者模式中用Dettach(),而事件的取消用“-=”。

8. 下面例子分别用观察者模式,事件机制来实现

  1)实例描述:客户支付了订单款项,这时财务需要开具发票,出纳需要记账,配送员需要配货。

  2)观察者模式的实现

 类图    

代码实现

/// <summary>    /// 抽象主题    /// </summary>    public interface ISubject    {        void Notify();    }    /// <summary>    /// 抽象观察者:工作岗位    /// </summary>    public abstract class JobStation    {        public abstract void Update();    }    /// <summary>    /// 具体主题,这里是客户    /// </summary>    public class Customer : ISubject    {        private string customerState;        private IList<JobStation> observers = new List<JobStation>();        /// <summary>        /// 增加观察者        /// </summary>        /// <param name="observer"></param>        public void Attach(JobStation observer)        {            this.observers.Add(observer);        }        /// <summary>        /// 移除观察者        /// </summary>        /// <param name="observer"></param>        public void Detach(JobStation observer)        {            this.observers.Remove(observer);        }        /// <summary>        /// 客户状态        /// </summary>        public string CustomerState        {            get { return customerState; }            set { customerState = value; }        }        public void Notify()        {            foreach (JobStation o in observers)            {                o.Update();            }        }    }    /// <summary>    ///具体观察者:会计    /// </summary>    public class Accountant : JobStation    {        private string accountantState;        private Customer customer;  //注册被观察者        public Accountant(Customer customer)        {            this.customer = customer;        }        /// <summary>        /// 更新状态        /// </summary>        public override void Update()        {            if (customer.CustomerState == "已付款")            {                Console.WriteLine("我是会计,我来开具发票。");                accountantState = "已开发票";            }        }    }    /// <summary>    ///具体观察者:出纳    /// </summary>    public class Cashier : JobStation    {        private string cashierState;        private Customer customer; //注册被观察者        public Cashier(Customer customer)        {            this.customer = customer;        }        public override void Update()        {            if (customer.CustomerState == "已付款")            {                Console.WriteLine("我是出纳员,我给登记入账。");                cashierState = "已入账";            }        }    }    /// <summary>    /// 具体观察者:配送员    /// </summary>    public class Dilliveryman : JobStation    {        private string dillivierymanState;        private Customer customer; //注册被观察者        public Dilliveryman(Customer customer)        {            this.customer = customer;        }        public override void Update()        {            if (customer.CustomerState == "已付款")            {                Console.WriteLine("我是配送员,我来发货。");                dillivierymanState = "已发货";            }        }    }

class Program    {        static void Main(string[] args)        {            Customer subject = new Customer();            subject.Attach(new Accountant(subject));            subject.Attach(new Cashier(subject));            subject.Attach(new Dilliveryman(subject));            subject.CustomerState = "已付款";            subject.Notify();            Console.Read();        }    }

运行结果:

    我是会计,我来开具发票。
    我是出纳员,我给登记入账。
    我是配送员,我来发货。

 

  3)事件实现

     类图

    

    通过类图来看,观察者和主题之间已经不存在任何依赖关系了。

    代码实现

/// <summary>    /// 抽象主题    /// </summary>    public interface ISubject    {        void Notify();    }    /// <summary>    /// 声明委托    /// </summary>    public delegate void CustomerEventHandler();    /// <summary>    /// 具体主题    /// </summary>    public class Customer : ISubject    {        private string customerState;        // 声明一个委托事件,类型为 CustomerEventHandler        public event CustomerEventHandler Update;        public void Notify()        {            if (Update != null)            {                // 使用事件来通知给订阅者                Update();            }        }        public string CustomerState        {            get { return customerState; }            set { customerState = value; }        }    }    /// <summary>    /// 财务,已经不需要实现抽象的观察者类,并且不用引用具体的主题    /// </summary>    public class Accountant    {        private string accountantState;        public Accountant()        { }        /// <summary>        /// 开发票        /// </summary>        public void GiveInvoice()        {            Console.WriteLine("我是会计,我来开具发票。");            accountantState = "已开发票";        }    }    /// <summary>    /// 出纳,已经不需要实现抽象的观察者类,并且不用引用具体的主题    /// </summary>    public class Cashier    {        private string cashierState;        public void Recoded()        {            Console.WriteLine("我是出纳员,我给登记入账。");            cashierState = "已入账";        }    }    /// <summary>    /// 配送员,已经不需要实现抽象的观察者类,并且不用引用具体的主题    /// </summary>    public class Dilliveryman    {        private string dillivierymanState;        public void Dilliver()        {            Console.WriteLine("我是配送员,我来发货。");            dillivierymanState = "已发货";        }    }



客户端代码

class Program    {        static void Main(string[] args)        {            Customer subject = new Customer();            Accountant accountant = new Accountant();            Cashier cashier = new Cashier();            Dilliveryman dilliveryman = new Dilliveryman();            // 注册事件            subject.Update += accountant.GiveInvoice;            subject.Update += cashier.Recoded;            subject.Update += dilliveryman.Dilliver;            /*             * 以上写法也可以用下面代码来替换            subject.Update += new CustomerEventHandler(accountant.GiveInvoice);            subject.Update += new CustomerEventHandler(cashier.Recoded);            subject.Update += new CustomerEventHandler(dilliveryman.Dilliver);             */            subject.CustomerState = "已付款";            subject.Notify();            Console.Read();        }    }


 运行结果

    我是会计,我来开具发票。
    我是出纳员,我给登记入账。
    我是配送员,我来发货。

9. 推模式与拉模式
  对于发布-订阅模型,大家都很容易能想到推模式与拉模式,用SQL Server做过数据库复制的朋友对这一点很清楚。在Observer模式中同样区分推模式和拉模式,我先简单的解释一下两者的区别:推模式是当有消息时,把消息信息以参数的形式传递(推)给所有观察者,而拉模式是当有消息时,通知消息的方法本身并不带任何的参数,是由观察者自己到主体对象那儿取回(拉)消息。知道了这一点,大家可能很容易发现上面我所举的例子其实是一种推模式的Observer模式。我们先看看这种模式带来了什么好处:当有消息时,所有的观察者都会直接得到全部的消息,并进行相应的处理程序,与主体对象没什么关系,两者之间的关系是一种松散耦合。但是它也有缺陷,第一是所有的观察者得到的消息是一样的,也许有些信息对某个观察者来说根本就用不上,也就是观察者不能“按需所取”;第二,当通知消息的参数有变化时,所有的观察者对象都要变化。鉴于以上问题,拉模式就应运而生了,它是由观察者自己主动去取消息,需要什么信息,就可以取什么,不会像推模式那样得到所有的消息参数。OK,说到这儿,你是否对于推模式和拉模式有了一点了解呢?我把前面的例子修改为了拉模式,供大家参考,可以看到通知方法是没有任何参数的:

public abstract class Stock{    private List<IObserver> observers = new List<IObserver>();    private String _symbol;    private double _price;    public Stock(String symbol, double price)    {        this._symbol = symbol;        this._price = price;    }    public void Update()    {        foreach (IObserver ob in observers)        {            ob.SendData();        }    }    public void AddObserver(IObserver observer)    {        observers.Add(observer);    }    public void RemoveObserver(IObserver observer)    {        observers.Remove(observer);    }    public String Symbol    {        get { return _symbol; }    }    public double Price    {        get { return _price; }    }}public class Microsoft : Stock{    public Microsoft(String symbol, double price)        : base(symbol, price)    { }}public interface IObserver{    void SendData();}public class Investor : IObserver{    private string _name;    private Stock _stock;    public Investor(string name,Stock stock)    {        this._name = name;        this._stock = stock;    }    public void SendData()    {        Console.WriteLine("Notified {0} of {1}'s " + "change to {2:C}",_name, _stock.Symbol, _stock.Price);    }}class Program{    static void Main(string[] args)    {        Stock ms = new Microsoft("Microsoft", 120.00);        ms.AddObserver(new Investor("Jom",ms));        ms.AddObserver(new Investor("TerryLee",ms));        ms.Update();        Console.ReadLine();    }}



当然拉模式也是有一些缺点的,主体对象和观察者之间的耦合加强了,但是这可以通过抽象的手段使这种耦合关系减到最小。

转自:http://www.cnblogs.com/wangjq/archive/2012/07/12/2587966.html

10.C++观察者模式举例

/************************************************************************/  /*                            观察者模式                                */  /************************************************************************/    #include <LIST>  #include <STRING>  #include <iostream>  using namespace std;    /*     编程要点:观察者接口要有一个更新自身状态的行为      被观察者(主题):要有一个存放与之相联系的观察者的链表,并有一个通知的行为,当被观察者的状态发生改变时,     它会通知所有与之相关联地具体观察者,即调用与之关联的观察者的Update方法  */    class Subject;    //观察者接口(抽象)  class Observer{  public:      virtual ~Observer(){};      virtual void Update(Subject *concreteSubject){};//根据被观察者更新自身状态        };    //被观察者(主题)  class Subject  {  private:      list<Observer*> *obvs;        public:      //构造函数      Subject()      {          obvs = new list<Observer*>;      }            void Attach(Observer *observer)      {          obvs->push_front(observer);//向链表中加入一个待通知的观察者      }            void Detach(Observer *observer)      {          obvs->push_back(observer);//删除一个观察者      }            //通知每个观察者      void Notify()      {          list<Observer*>::iterator it;          it = obvs->begin();          for(;it!=obvs->end();it++){              (*it)->Update(this);//每个观察者被通知后更新自己的状态          }      }  public:      virtual char* getState(){return NULL;}       virtual void setState(){}  };      /*具体的主题*/  class ConcreteSubject : public Subject  {  private:      char *state;//被观察者的状态        public:      /*被观察者自身状态的设置与获取*/      char* getState()      {          return state;      }            void setState(char *state)      {          this->state = state;      }  };        //具体的观察者  class ConcreteObserverA : public Observer  {  private:      char *name;//名称      char *state;//状态    public:      ConcreteObserverA(char *name)      {          this->name = name;      }        void Update(Subject *concreteSubject)      {          state = concreteSubject->getState();//更新自身状态          cout<<"观察者:"<<name<<"的新状态是:"<<state<<endl;      }      };    void main()  {      //构造一个主题      ConcreteSubject *concreteSubject = new ConcreteSubject();      //让主题和观察者相联系      concreteSubject->Attach(new ConcreteObserverA("X"));      concreteSubject->Attach(new ConcreteObserverA("Y"));        //主题状态改变,并通知观察者      concreteSubject->setState("老师走了,大家可以玩了,哈哈");      concreteSubject->Notify();         printf("过了一段时间后........................\n");        //主题状态改变,再次通知观察者      concreteSubject->setState("老师来了,大家别说了");      concreteSubject->Notify();  }  


运行结果:

	
				
		
原创粉丝点击