【设计模式系列】行为型模式之Observer模式

概要
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模式来解决。





posted @ 2012-05-23 21:59  MXi4oyu  阅读(198)  评论(0编辑  收藏  举报