概要 Observer模式算是一种大名鼎鼎的设计模式了,如果你还没听说过Observer模式,那你总多少听说过MVC模式吧?其实MVC就是基于Observer模式的细化和扩展。所以如果要理解MVC,就应该首先掌握Observer模式。Observer模式反映的是一种面向对象的一对多的事件触发关系,当某个对象希望在发生某种变化时能通知其他多个对象,而这个对象又不希望跟他希望通知的其他对象产生耦合时,Observer模式会是一种很好的解决方法。

目的 在对象间建立一对多的对应关系,当发生某种变化时可以通知已建立关系的多个对象。而对象间本身不产生任何耦合。

实例 Observer模式的例子其实有很多,所有涉及事件通知的机制几乎都可以使用Observer模式来实现。在这里就看这样一个例子吧。 假设我们有一个系统,当有任何用户登录成功时,都需要触发如下动作: 1. 显示该用户信息到页面UI中 2. 在Database中为该用户创建用户空间 3. 广播给其他已登录用户该用户登录了 4. 其他

千万不要告诉我,你会这样设计:直接从Login模块去强关联其他需要动作的模块。 这样的设计把所有模块搞的一团浆糊,会让以后的任何扩展,变更都举步维艰。看看Observer模式会怎么做吧。


class Observer { public:      virtual void Update(int event) = 0; };


所有需要被触发的对象都从Observer类继承,并重写Update方法。


const int EVENT_LOGIN = 1; class UIMng : public Observer { public:      virtual void Update(int event) {           if (EVENT_LOGIN  == event) {                ......                     }      } }; class DatabaseMng: public Observer { public:      virtual void Update(int event); }; class BroadcastMng: public Observer { public:      virtual void Update(int event); }; DatabaseMng, BroadcastMng等都和UIMng类类似。  class Subject { public:      void attach(Observer* o) {           if (o != NULL) {               mObservers.push_back(o);           }      }      void detach(Observer* o) {           list<Observer*>::iterator it;           if (o != NULL) {                for (it = mObservers.begin(); it !=mObservers.end(); it++) {                     if (*it == o) {                         mObservers.erase(it);                     }                }           }      }      void Notify(int event) {           list<Observer*>::iterator it;           for (it =mObservers.begin(); it !=.end(); it++) {                if (*it != NULL) {                     (*it)->Update(event);                }           }      } private:      list<Observer*> mObservers; };


Subject类提供了attach和detach方法来增加或删除需要绑定的观察者对象,而Notify方法则会触发执行所有已绑定对象的Update方法。需要Observer模式支持的可以从Subject类继承。


class LoginSubject : public Subject { public:      void OnLogin() {           Notify(EVENT_LOGIN);      } };


login成功调用OnLogin方法时会触发所有被绑定的对象。

client调用方代码如下:

Observer* uiMng = new UIMng(); Observer* dbMng = new DatabaseMng(); Observer* bcMng = new BroadcastMng(); LoginSubject login; login.attach(uiMng); login.attach(dbMng ); login.attach(bcMng );


当login对象的OnLogin被调用时,所有绑定的Observer对象都会被触发并调用其Update方法来进行响应。

上面的实例中从Subject把事件类型传递给了Observer,当Observer被绑定于多种事件时,可以通过传递事件类型类进行区别处理。其实很多时候索性定义多种Observer对象会让逻辑更清晰,比如这里是login事件的话就把这种Observer定义为LoginObserver,当还需要其他事件观察者时,再定义其他观察者。

class LoginObserver{ public:      virtual void Update() = 0; };


而在实际应用中,从Subject到Observer数据的流动通常有两种方式, 一种是Push的方式,Push方式是指数据被Subject主动的传递给了Observer,比如上面实例中Subject 就通过参数直接把事件类型传递给了Observer。

另一种是Pull的方式,Pull方式相反,它是由Observer主动从Subject获取相关数据。代码会有如下变化:

class Subject { public:      void attach(Observer* o) {           if (o != NULL) {               mObservers.push_back(o);           }      }      void detach(Observer* o) {           list<Observer*>::iterator it;           if (o != NULL) {                for (it = mObservers.begin(); it !=mObservers.end(); it++) {                     if (*it == o) {                         mObservers.erase(it);                     }                }           }      }      void Notify() {           list<Observer*>::iterator it;           for (it =mObservers.begin(); it !=.end(); it++) {                if (*it != NULL) {                     (*it)->Update(this);                }           }      }      int GetStatus() {           ......      } private:      list<Observer*> mObservers; }; class UIMng : public Observer { public:      virtual void Update(Subject* sub) {           ......           int status = sub->GetStatus();           ......      } };


在Notify中,每次调用Update都把Subject对象本身传递过去,而在Update方法中会有Observer主动去Pull相关数据。上面代码中UIMng取得了Subject中的Status信息。

Push方式的特点是,不管Observer是否需要都会把数据Push给Observer,处理逻辑简单,但当数据量大时多少会影响性能。 Pull方式的特点是,由Observer根据自己的需求自己去从Subject取数据,可以忽略不需要的数据,避免冗余,但处理逻辑较复杂,比较容易出错。

应用 前面也已经提到过,Observer模式的应用极广,很多一对多的响应逻辑几乎都可以用Observer模式来解决。