22. 观察者模式
一、观察者模式
观察者模式(Observer Pattern)定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式的别名包括 发布-订阅模式(Publish/Subscribe)、模型-视图模式(Model/View)、源-监听器模式(Source/Listener)或 从属者模式(Dependents)。
观察者模式用于建立对象与对象之间的依赖关系。一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。在观察者模式中,发生改变的对象称为观察目标,而被通知的对象称为观察者。一个观察目标可以对应多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。
观察者模式的主要角色如下:
- 目标(Subject):目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意数量的观察者来观察,它提供一系列方法来增加和删除观察者对象,同时定义了通知方法 notify()。目标类可以是接口,也可以是抽象类或具体类。
- 具体目标(Concrete Subject):具体目标是目标类的子类,通常包含有经常发生改变的数据。当它的状态发生改变时,向其各个观察者发出通知。同时它还实现了在目标类中定义的抽象业务逻辑方法(如果有)。如果无须扩展目标类,则具体目标类可以省略。
- 观察者(Observer):观察者将对观察目标的改变做出反应。观察者一般定义为接口,该接口声明了更新数据的方法,因此又称为抽象观察者。
- 具体观察者(Concrete Observer):在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致。它实现了在抽象观察者中声明的更新数据的方法。通常在实现时,可以调用具体目标类的注册方法将自己添加到目标类的集合中或通过注销方法将自己从目标类的集合中删除。
观察者模式描述了如何建立对象与对象之间的依赖关系,以及如何构造满足这种需求的系统。观察者模式包含观察目标和观察者两类对象。一个目标可以有任意数目的与之相依赖的观察者,一旦观察目标的状态发生改变,所有的观察者都将得到通知。作为对这个通知的响应,每个观察者都将监视观察目标的状态以使其状态与目标状态同步,这种交互也称为发布-订阅(Publish-Subscribe)。观察目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅它并接收通知。
二、C++实现观察者模式
首先定义一个抽象目标。
// 目标类
class AllyControlCenter
{
private:
std::string allyName;
std::vector<Observer *> players;
public:
AllyControlCenter(std::string allyName);
void join(Observer * observer); // 注册方法
void quit(Observer * observer); // 注销方法
virtual void notify(std::string name) = 0; // 通知方法
std::string getAllyName(void);
void setAllyName(std::string allyName);
std::vector<Observer *> getPlayers(void);
void setPlayers(std::vector<Observer *> players);
};
AllyControlCenter::AllyControlCenter(std::string allyName) : allyName(allyName)
{
std::cout << "【" << allyName << "】战队组建成功!" << std::endl;
}
void AllyControlCenter::join(Observer *observer)
{
players.push_back(observer);
std::cout << "【" << observer->getName() << "】加入到【" << getAllyName() << "】的战队中。" << std::endl;
}
void AllyControlCenter::quit(Observer *observer)
{
for (auto it = players.begin(); it != players.end(); it++)
{
if (*it == observer)
{
players.erase(it);
std::cout << "【" << observer->getName() << "】退出了【" << getAllyName() << "】的战队。" << std::endl;
}
}
}
std::string AllyControlCenter::getAllyName(void)
{
return allyName;
}
void AllyControlCenter::setAllyName(std::string allyName)
{
this->allyName = allyName;
}
std::vector<Observer *> AllyControlCenter::getPlayers(void)
{
return players;
}
void AllyControlCenter::setPlayers(std::vector<Observer *> players)
{
this->players = players;
}
具体目标类是实现抽象目标类的一个具体子类。
// 具体目标类
class ConcreteAllyControlCenter : public AllyControlCenter
{
public:
using AllyControlCenter::AllyControlCenter;
void notify(std::string name) override;
};
void ConcreteAllyControlCenter::notify(std::string name)
{
std::cout << "【" << getAllyName() << "】战队紧急通知:【" << name << "】遭受敌人攻击。" << std::endl;
// 遍历观察者集合,调用每一个盟友(自己除外)的支援方法
for (auto it : getPlayers())
{
if (it->getName() != name)
{
it->help();
}
}
}
抽象观察者角色一般定义为一个接口,通常只声明一个更新数据方法,为不同观察者的更新(响应)行为定义相同的接口。这个方法在其子类中实现,不同的观察者具有不同的响应方法。
// 抽象观察者
class Observer
{
private:
std::string name;
public:
Observer(std::string name);
virtual void help(void) = 0; // 支援同伴的方法
virtual void beAttacked(AllyControlCenter * ally) = 0; // 受到攻击的方法
std::string getName(void);
void setName(std::string name);
};
Observer::Observer(std::string name) : name(name) {}
std::string Observer::getName(void)
{
return name;
}
void Observer::setName(std::string name)
{
this->name = name;
}
在具体观察者中实现了更新数据的方法。
// 具体观察者
class Player : public Observer
{
public:
using Observer::Observer;
void help(void) override;
void beAttacked(AllyControlCenter * ally) override;
};
void Player::help(void)
{
std::cout << "坚持住,【" << getName() << "】正在赶来。" << std::endl;
}
void Player::beAttacked(AllyControlCenter * ally)
{
std::cout << "请求支援,【" << getName() << "】被攻击了。" << std::endl;
ally->notify(getName());
}
main() 函数:
#include <iostream>
#include <vector>
int main(void)
{
AllyControlCenter * team1 = new ConcreteAllyControlCenter("涂山");
Observer * player1 = new Player("涂山红红");
Observer * player2 = new Player("涂山雅雅");
Observer * player3 = new Player("涂山容容");
team1->join(player1);
team1->join(player2);
team1->join(player3);
player1->beAttacked(team1);
team1->quit(player1);
player1->setName("涂山苏苏");
team1->join(player1);
delete player1;
delete player2;
delete player3;
delete team1;
return 0;
}
在有些更加复杂的情况下,具体观察者类的更新数据的方法在执行时需要使用到具体目标类中的状态(属性)。因此,在具体目标类与具体观察值之间有时候还存在关联或依赖关系。
在具体观察者中定义一个具体目标类实例,通过该实例获取存储在具体目标类中的状态。如果具体观察者的更新数据的方法不需要使用到具体目标类中的状态属性,则可以对观察者模式的标准结构进行简化,在具体观察者和具体目标之间无须维持对象引用。
如果在具体层具有关联关系,系统的扩展性将受到一定的影响,增加新的具体目标类有时候需要修改原有观察者的代码,在一定程度上违反了开闭原则。但是,如果原有观察者类无须关联新增的具体目标,则系统扩展性不受影响。
三、观察者模式的总结
3.1、观察者模式的优点
- 观察者模式可以实现表示层和数据逻辑层的分离。它定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当具体观察者角色。
- 观察者模式在观察目标和观察者之间建立一个抽象的耦合。观察目标只需要维持一个抽象观察者的集合,无须了解其具体观察者。由于观察目标和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。
- 观察者模式支持广播通信。观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。
- 观察者模式满足开闭原则的要求,增加新的具体观察者无须修改原有系统代码。在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。
3.2、观察者模式的缺点
- 如果一个观察目标对象有很多直接和间接观察者,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
3.3、观察者模式的适用场景
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致一个或多个其他对象也发生改变,而并不知道具体有多少对象将发生改变,也不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……可以使用观察者模式创建一种链式触发机制。