一、定义
观察者模式在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新。
二、要点
- 观察者模式定义了对象之间的一对多关系
- 主题(可观察者)用一个共同的接口来更新观察者
- 可观察者不知道观察者的细节,只知道观察者实现了观察者接口
- 使用此模式时,你可从被观察者处推(push)或者拉(pull)数据(然而,推的方式被认为更“正确”)
- 有多个观察者时,不可以依赖特定的通知次序
三、类图
四、代码
1. 采用“推(push)”的方式:
class Subject {
public:
Subject(){};
virtual ~Subject(){};
void registerObserver(Observer *o);
void removeObserver(Observer *o);
virtual void notifyObservers() = 0;
protected:
vector<Observer*> observers;
};
void Subject::registerObserver(Observer *o){
observers.push_back(o);
}
void Subject::removeObserver(Observer *o){
remove(observers.begin(), observers.end(), o);
}
class ConcreteSubject : public Subject {
public:
ConcreteSubject():changed(false),data1(0),data2(0.0),data3(""){};
~ConcreteSubject(){};
void notifyObservers();
bool isChanged();
void setChanged();
void changeData(int d1, float d2, string d3);
void dataChanged();
private:
bool changed;
int data1;
float data2;
string data3;
};
void ConcreteSubject::notifyObservers(){
if(isChanged())
{
for(vector<Observer*>::iterator it = observers.begin(); it != observers.end(); ++it){
it->update(data1, data2, data3);
}
changed = false;
}
}
void ConcreteSubject::changeData(int d1, float d2, string d3){
data1 = d1;
date2 = d2;
data3 = d3;
dataChanged();
}
void ConcreteSubject::dataChanged(){
setChanged();
notifyObservers();
}
void ConcreteSubject::setChanged(){
changed = true;
}
bool ConcreteSubject::isChanged(){
return changed;
}
class Observer {
public:
Observer(){}
~Observer(){}
virtual void update(int d1, float d2, string d3) = 0;
};
class ConcreteObserver : public Observer {
public:
ConcreteObserver(Subject *pSubject);
~ConcreteObserver(){}
void update(int d1, float d2, string d3);
void display();
void unregister();
private:
Subject *pSubject; //为了以后取消注册方便,保留此引用
int data1;
float data2;
};
ConcreteObserver::ConcreteObserver(Subject *pSubject){
if(pSubject){
this.pSubject = pSubject;
this.pSubject.registerObserver(this);
}
}
void ConcreteObserver::unregister(){
if(pSubject){
this.pSubject.removeObserver(this);
}
}
void display(){
printf("data1:%d, data2:%f\n", data1, data2);
}
void update(int d1, float d2, string d3){
data1 = d1;
data2 = d2;
}
2. 采用“拉(pull)”的方式
class DataObj{
};
class ConcreteSubjectDataObj : public DataObj
{
public:
int data1;
float data2;
string data3;
};
class Subject {
public:
Subject():type("Subject"){};
virtual ~Subject(){};
void registerObserver(Observer *o);
void removeObserver(Observer *o);
virtual void notifyObservers() = 0;
void getType(){return type;}
protected:
vector<Observer*> observers;
string type;
};
void Subject::registerObserver(Observer *o){
observers.push_back(o);
}
void Subject::removeObserver(Observer *o){
remove(observers.begin(), observers.end(), o);
}
class ConcreteSubject : public Subject {
public:
ConcreteSubject():changed(false),type("ConcreteSubject"){
dataObj.data1 = 0;
dataObj.data2 = 0.0;
dataObj.data3 = "";
};
~ConcreteSubject(){};
void notifyObservers(int type = 0); // 0 for push, 1 for pull
bool isChanged();
void setChanged();
void changeData(ConcreteSubjectDataObj *pData);
void dataChanged();
//a group of getter method
int getData1(){return pData->data1;}
float getData2(){return pData->data2;}
string getData3(){return pData->data3;}
private:
bool changed;
ConcreteSubjectDataObj dataObj;
};
void ConcreteSubject::notifyObservers(int type){
if(isChanged())
{
ConcreteSubjectDataObj *pDataObj = (0 == type) ? &dataObj : NULL;
for(vector<Observer*>::iterator it = observers.begin(); it != observers.end(); ++it){
it->update(this, pDataObj);
}
changed = false;
}
}
void ConcreteSubject::changeData(ConcreteSubjectDataObj *pData){
this.dataObj = *pData;
dataChanged();
}
void ConcreteSubject::dataChanged(){
setChanged();
/* pull */
notifyObservers(1);
/** push
notifyObservers(0);
*/
}
void ConcreteSubject::setChanged(){
changed = true;
}
bool ConcreteSubject::isChanged(){
return changed;
}
class Observer {
public:
Observer(){}
virtual ~Observer(){}
virtual void update(Subject *pSubject, DataObj *pDataObj) = 0;
};
class ConcreteObserver : public Observer {
public:
ConcreteObserver(Subject *pSubject);
~ConcreteObserver(){}
void update(Subject *pSubject, DataObj *pDataObj);
void display();
void unregister();
private:
Subject *pSubject; //为了以后取消注册方便,保留此引用
int data1;
float data2;
};
ConcreteObserver::ConcreteObserver(Subject *pSubject){
if(pSubject){
this.pSubject = pSubject;
this.pSubject.registerObserver(this);
}
}
void ConcreteObserver::unregister(){
if(pSubject){
this.pSubject.removeObserver(this);
}
}
void display(){
printf("data1:%d, data2:%f\n", data1, data2);
}
void update(Subject *pSubject, DataObj *pDataObj){
if(!pSubject->getType().compare("ConcreteSubject")){
data1 = pSubject->getData1();
data2 = pSubject->getData2();
}
}
五、疑问解答
1. 关于采用推方式和拉方式推方式:
优点:可以在一次通知中一口气得到所有东西;
缺点:有可能造成只需要一点点数据的类被强迫收到一堆数据;
拉方式:
优点:
- 需要什么数据就拉什么数据,不会有冗余数据;
- 当主题增加新状态时,只需要增加相应的getter方法,不需要修改和更新每位观察者的调用
- 被观察者门户大开,不管是注册还是未注册的观察者都能获取被观察者的数据;
- 有可能需要调用很多次getter方法才能收集到需要的所有数据
六、参考文献
1. Head First设计模式