定义一对多的依赖关系,使得当一个对象发生改变时,其它相关对象能自动得到通知并更新自身的状态。

结构图:

    上图中,由一个Subject(或者由其派生的)类对象维持任意数量的Observer(或其派生)类对象与它的联系,当Subject对象的状态发生变化时,所有相关的Observer对象都会获得一个更新通知(Subject::Notify),并通过查询Subject的状态来保持同步(另一种方式是,Subject直接发生变化的状态在Notify中推送(push)到Observer对象中,后文会述及)。 

    具体而言,以内容订阅为例:一个内容提供者Provider(ConcreteSubject),n个订阅者Subscriber(ConcreteObserver)。Provider维持所有订阅了它的Subscriber的引用,当它的内容发生变化时,需要通知这些Subscriber进行同步: 

    基类Subject类是接口类,它负责提供注册(Attach)功能给那些需要订阅它的订阅者,以及相应的注销功能(Detach),以及负责通知(Notify)所有已经注册了的订阅者,使它们根据需要通过查询内容来同步各自的状态。

 1 class Subject
 2 {
 3 public:
 4     Subject(void){};
 5     ~Subject(void){};
 6     void Attach(Observer* o){
 7             _memberList.push_back(o);
 8          o->Update();
 9         }
10     void Detach(Observer* o){
11             _memberList.erase(find(_memberList.begin(),
12                                    _memberList.end(),o));
13         }  
14     virtual void Notify(void)
15     {
16         for (size_t i=0; i<_memberList.size(); i++){
17             _memberList[i]->Update();
18         }
19     }
20 protected:
21     vector<Observer*> _memberList;
22 };

    Provider类:保存可供的内容(_state),并为订阅者提供查询操作(GetState),以及改变自身内容的操作(SetState),在改变内容后,立即通知各个订阅者进行同步(如果有多个内容供订阅,可以重写Notify函数以选择性的只通知改变了的内容,但这会增加Provider和Subscriber类的耦合性)。

 1 class Provider :public Subject
 2 {
 3 public:
 4     Provider(string s):_state(s){};
 5     string GetState(void){
 6         return _state;
 7     }
 8     void SetState(string s){
 9         _state = s;
10         Notify();
11     }
12 private:
13     string _state;
14 };

    Observer类只提供一个Update接口:

1 class Observer
2 {
3 public:
4     Observer(void){};
5     ~Observer(void){};
6     virtual void Update(void){};
7 };

    Subscriber类派生自Observer类,实现了Update接口,通过Provider提供的查询函数进行查询更新状态。其中_name成员的作用只是在这里提供一个验证Detach操作的功能的作用。

 1 class Subscriber :public Observer
 2 {
 3 public:
 4     Subscriber(Provider* p,string n)
 5         :_provider(p),_name(n){
 6         p->Attach(this);
 7     };
 8     void Update(void){
 9         _observerState = _provider->GetState();
10         cout<<_name<<": "<<_observerState<<endl;
11     }
12     Provider* _provider;
13 private:
14     string _name;
15     string _observerState;
16 };

下面再看测试代码:

Subscriber的构造函数多出了一个字符串常量,以在输出结果的时候区分各个对象

 1 Provider *p = new Provider("Init state"); 
 2 Subscriber *o1 = new Subscriber(p,"o1");
 3 Subscriber *o2 = new Subscriber(p,"o2");
 4 Subscriber *o3= new Subscriber(p,"o3");
 5 
 6 p->SetState("Second state");
 7 
 8 o2->_provider->Detach(o2);
 9 
10 p->SetState("Last state");

下面是程序运行后的输出结果:

应用场景:

1、当改变一个对象需要对其它未知数量的对象进行同步的变化时;

2、当一个对象应该能够通知其它未知对象进行改变时;

3、当一个具有两个属性的抽象事物,其中一个属性依赖于另一个,将这两个属性独立封装可以独立的对它们进行改变和复用。

实现要点:

      Push vs Pull:

            Push model 由Subject将变化的信息直接传递给Observers,这样会增加Subject与Observer的耦合性,使得Observer对象较难复用;

     Pull model 由Subject提供少量的信息给Observers然后由Observer决定更新哪些信息,这样减少了耦合性,但是在效率上比push model有所损失。

     多Subject模式:

            可以通过对Subscriber中的成员变量_provider进行一些变化并添加一些管理代码来实现。

       通知的发起者:

            由Subject在状态改变后发送,好处是操作透明,坏处是每一次改变都会发出通知,可能造成低效操作;

            由Client发送,好处是可以选择发送时机,比如在完成了对Subject状态的一系列改变后才发送,坏处是可能会忘记发送。 

参考资料:

      《Head First Design Patterns》

      《设计模式——可复用面向对象软件的基础》