1. 什么是观察者模式?
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变的时候,所有依赖于它的对象都得到通知并被自动更新。又被称为发布—订阅模式。
观察者模式的本质:触发联动。
当修改目标对象的状态的时候,就会触发相应的通知,然后就会循环调用所有注册观察者对象的相应方法;并且这个联动还是动态的,可以通过注册和取消注册来控制观察者;目标对象和观察者对象的解耦,无论观察者发生了怎样的变化,目标对象总是能够正确地联动起来;
目标和观察者之间是典型的一对多的关系。
一个观察者也可以观察多个目标,如果观察者为多个目标定义的通知更新方法都是update方法的话,这样就会很麻烦,因为需要接收多个目标的通知,则就需要在update方法内部进行区分,这个更新的通知来自于哪一个目标,不同的目标就有着不同的操作。
单向依赖:
- 观察者和目标是单向依赖的,只有观察者依赖于目标,而目标不会依赖于观察者;
- 它们之间的联系主动权始终掌握在目标手中,只有目标知道什么时候需要通知观察者,而观察者始终是被动的,被动的等待目标的通知,等待目标传值给它;
- 对于目标而言,所有的观察者都是一样的,目标会被一视同仁的对待。当然也可以进行控制,只不过那样就不是原始的观察者模式了。
实现细节:
- 目标实现对象要维护观察者的注册信息;
- 目标实现对象需要维护引起通知的状态,一般是目标自身的状态;
- 观察者实现对象要能接收目标的通知,能够接收目标传递的数据,或者主动去获取目标的数据,方便后续处理;
- 如果一个观察者观察多个目标,那么在观察者的更新方法里面,需要去判断是来自于哪一个目标的通知。
class Observer
{
public:
virtual string GetName() = 0;
virtual void Update(const string&) = 0;
};
class Subject
{
public:
virtual void addObserver(Observer* observer) = 0;
virtual void RemoveObserver(Observer* observer) = 0;
virtual void NotifyObserver(const string&) = 0;
};
class ObserverFactory : public Observer
{
private:
string _s;
public:
ObserverFactory(const string &s)
:_s(s)
{}
~ObserverFactory()
{
_s.clear();
}
virtual string GetName()
{
return _s;
}
virtual void Update(const string& s)
{
cout << _s << " 收到了数据更新: " << s << endl;
}
};
class SubjectFactory : public Subject
{
private:
vector<Observer*> _vo;
public:
~SubjectFactory()
{
_vo.clear();
}
virtual void addObserver(Observer* observer)
{
auto it = find(_vo.begin(), _vo.end(), observer);
if (it != _vo.end())
{
cout << "该观察者已经存在,无需添加" << endl;
}
else
{
_vo.push_back(observer);
}
}
virtual void RemoveObserver(Observer* observer)
{
auto it = find(_vo.begin(), _vo.end(), observer);
if (it == _vo.end())
{
cout << "该观察者不存在,无需删除" << endl;
}
else
{
_vo.erase(it);
}
}
virtual void NotifyObserver(const string& s)
{
for (auto& e : _vo)
{
e->Update(s);
}
}
};
int main()
{
Subject* aim = new SubjectFactory();
Observer* reader1 = new ObserverFactory("reader1");
Observer* reader2 = new ObserverFactory("reader2");
Observer* reader3 = new ObserverFactory("reader3");
aim->addObserver(reader1);
aim->addObserver(reader2);
aim->addObserver(reader3);
aim->NotifyObserver("该学习了");
aim->RemoveObserver(reader3);
aim->NotifyObserver("该学C++了");
delete reader1;
delete reader2;
delete reader3;
delete aim;
return 0;
}
2. 推模型 和 拉模型
推模型:目标对象主动向观察者推送目标的详细信息,不管观察者是否需要,推送的信息通常是目标对象的全部或部分数据,相当于是在广播通信;
拉模型:目标对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体地信息,由观察者主动到目标对象中获取,相当于是观察者从目标对象中拉数据。在这种情况下,会把目标对象自身通过update方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过引用来获取了。
模型比较:
- 推模型假定目标对象知道观察者需要的数据;而拉模型是目标对象不知道观察者具体需要什么数据,没有办法的情况下,干脆将自身传递给观察者,去观察者去按需取值;
- 推模型可能会使得观察者对象难以复用,因为观察者定义的update方法是按需而定的,可能无法兼顾到没有考虑的情况。那么就需要重新实现观察者或者提供新的update方法,而拉模型不会出现这种情况。
3. 观察者模式优缺点
优点:
- 观察者和目标之间的抽象耦合:目标只知道观察者接口,并不知道具体的观察者的类;
- 动态联动:一个操作会引起其它相关的操作;
- 广播通信:目标发送通知给观察者是面向所有注册的观察者,所以每次通知的信息就要对所有注册的观察者进行广播;当然也可以限制广播范围;
缺点:
4. 可能会引起无谓的操作:由于观察者模式是广播通信,不管观察者需不需要,每个观察者都会调用update方法,如果观察者不需要执行此次操作,那么这次操作就浪费了;也可能引起误操作。
4. 何时选用观察者模式?
- 当一个抽象模型中,一个操作会影响另一个状态的变化时,那么就可以选用观察者模式,将两者封装为观察者和目标对象;
- 如果更改一个对象,需要同时连带更改其它对象时,并且不知道有多少对象会被连带更改。被更改的作为目标对象,被连带更改的作为观察者对象;
- 当一个对象必须通知其它对象,但这个对象和其它被通知的对象是松散耦合的。这个对象就是目标对象,被它通知的对象就是观察者对象。