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

上图中,由一个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》
《设计模式——可复用面向对象软件的基础》