【设计模式系列】行为型模式之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模式来解决。