第21章 行为型模式—观察者模式
Posted 浅墨浓香
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第21章 行为型模式—观察者模式相关的知识,希望对你有一定的参考价值。
1. 观察者模式(Observer Pattern)的定义
(1)定义:定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
(2)观察者模式的结构和说明(拉模型)
①Subject:目标对象,通常具如的功能:一个目标可以被多个观察者观察;目标提供对观察者的注册和退订的维护;当目标的状态发生变化时,目标负责通知所有注册的、有效的观察者。
②Observer:定义观察者的接口,提供目标通知时对应的更新方法,这个更新方法进行相应的业务处理,可以在这个方法里面回调目标对象,以获取目标对象的数据。
③ConcreteSubject:具体的目标实现对象,用来维护目标状态,当目标对象的状态发生改变时,通知所有注册的、有效的观察者,让观察者执行相应的处理。
④ConcreteObserver:观察者的具体实现对象,用来接收目标的通知,并进行相应的后续处理,比如更新自身状态以保持和目标的相应状态一致。
【编程实验】图书促销活动
//行为型模式——观察者模式 //场景:图书促销活动(拉模型) /* 观察者模式在现实的应用系统中也有好多应用,比如像当当网、京东商城一类的电子商务网站, 如果你对某件商品比较关注,可以放到收藏架,那么当该商品降价时,系统给您发送手机短信或邮件。 这就是观察者模式的一个典型应用,商品是被观察者,有的叫主体;关注该商品的客户就是观察者 */ #include <iostream> #include <string> #include <list> using namespace std; class Subject; //前向声明(被观察者) //******************************观察者接口******************************** class Observer { public: virtual void update(Subject* subject) = 0; }; //******************************目标对象(Subject)*************************** //抽象目标对象(被观察者) class Subject //Java中命名为Observable { private: bool changed; list<Observer*> obs; public: void setChanged() { changed = true; } void clearChanged() { changed = false; } bool hasChanged() { return changed; } void addObserver(Observer* o) { obs.push_back(o); } void delObserver(Observer* o) { obs.remove(o); } void notifyObservers() { list<Observer*>::iterator iter = obs.begin(); while(iter != obs.end()) { (*iter)->update(this); ++iter; } } }; //书——被观察者 class Book : public Subject { private: string name; double price; public: string& getName() { return name; } void setName(string value) { name = value; } double getPrice() { return price; } void setPrice(double value) { price = value; } //当书的价格修改时调用该方法 void modifyPrice() { //调用父类方法,改变被观察者的状态 setChanged(); //通知客户该书己降价 notifyObservers(); } }; //*****************************具体的观察者***************************** //具体观察者(一般顾客,假设只留手机号码) class Buyer : public Observer { private: string buyerId; string mobileNo; public: string& getBuyerId(){return buyerId;} void setBuyerId(string value) {buyerId = value;} string& getMobileNo(){return mobileNo;} void setMobileNo(string value){mobileNo = value;} //收到通知时要进行的操作 void update(Subject* s) { Book* b = (Book*)s; cout <<"给一般顾客(" <<buyerId <<")发的手机短信:"<< b->getName() <<"降价了,目前的价格为" << b->getPrice()<<"元" << endl; } }; //具体观察者(新华书店,假设只留email) class BookStore : public Observer { private: string buyerId; string email; public: string& getBuyerId(){return buyerId;} void setBuyerId(string value) {buyerId = value;} string& getEmail(){return email;} void setEmail(string value){email = value;} //收到通知时要进行的操作 void update(Subject* s) { Book* b = (Book*)s; cout <<"给新华书店(" <<buyerId <<")发的电子邮件:"<< b->getName() <<"降价了,目前的价格为" << b->getPrice()<<"元" << endl; } }; int main() { //书促销活动 Book bk; bk.setName("《设计模式:可复用面向对象软件的基础》"); bk.setPrice(45.00); //假设原价是60,现在是降价促销 //一般客户 Buyer by; by.setBuyerId("001"); by.setMobileNo("1359912XXXX"); //新华书店 BookStore bs; bs.setBuyerId("002"); bs.setEmail("xyz@qq.com"); //增加观察者,在实际应用中就是哪些人对该书做了关注 bk.addObserver(&by); //一般客户对该对做了关注 bk.addObserver(&bs); //新华书店对该书做了关注 //发送降价通知 bk.modifyPrice(); return 0; } /* 给一般顾客(001)发的手机短信:《设计模式:可复用面向对象软件的基础》降价了,目前的价格为45元 给新华书店(002)发的电子邮件:《设计模式:可复用面向对象软件的基础》降价了,目前的价格为45元 */
2. 思考观察者模式
(1)观察者模式的本质:触发联动。当修改目标对象的状态时,就会触发相应的通知,然后会循环调用所有观察者对象的相应方法,通知这些观察者,让其做相应的反应。其实就相当于联动调用这些观察者的方法。它将目标对象与观察者解耦,这样目标对象与观察者可以独立变化,但又可以正确联动起来。
(2)目标对象和观察者之间的关系
①一个目标只有一个观察者,也可以被多个观察者观察。
②一个观察者可以观察多个目标对象,但这里一般观察者内要提供不同的update方法,以便让不同的目标对象来回调。
③在观察者模式中,观察者和目标是单向依赖的,只有观察者依赖于目标,而目标是不依赖于具体的观察者(只依赖于接口)。
④它们之间的联系主动权掌握在目标对象手中,只有目标对象知道什么时候需要通知观察者。在整个过程中,观察者始终是被动地等待目标对象的通知。
⑤对于目标对象而言,所有的观察者都是一样的,会一视同仁。但也可以在目标对象里面进行控制,实现有区别的对待观察者。
(3)基本的实现说明
①具体的目标实现对象要能维护观察者的注册信息,最简单的实现方案就是采用链表来保存观察者的注册信息。
②具体的目标实现对象需要维护引起通知的状态,一般情况下是目标自身的状态。变形使用的情况下,也可以是别的对象的状态。
③具体的观察者实现对象需要能接收目标的通知,能够接收目标传递的数据或主动去获取目标的数据,并进行后续处理。
④如果是一个观察者观察多个目标,那在观察者的更新方法里面,需要去判断是来自哪一个目标的通知。一种简音的解决方案是扩展update方法,比哪里在方法里多传递一个参数进行区分。还有一种更简单的方法,就是定义不同的回调方法。
(4)触发通知的时机
①一般是在完成状态维护后触发,因为通知会传递数据,不能够先通知后改数据,这很容易导致观察者和目标对象的状态不一致。
②可能出错的示例代码片段
void setContent(string content) { //目标先发出通知,再修改自己的数据,会造成问题 notifyAllObservers(); //先发通知(此时可能有的观察者就来取数据,结果 //目标的数据还没更新)。 this->content = content; //再修改自身数据 }
(5)相互观察
在某些应用中,可能会出现目标和观察者相互观察的情况。这种情况要防止可能出现的死循环现象。
3. 推模型和拉模型
(1)推模型:目标对象主动向观察者推送目标的详细信息,不管观察者是否需要,推送的消息通常是目标对象的全部或部分数据。相当于是在广播通知。
(2)拉模型:目标对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到目标对象中获取,相当于是观察者从目标对象中拉数据。一般这种模型的实现中,会把目标对象自己通过update方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取。
(3)关于两种模型的比较
①推模型是假定目标对象知道观察者需要的数据;而拉模型是目标对象不知道观察者具体需要什么数据,在没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需取值。
②推模式可能会使观察者对象难以复用,因为观察者定义的update方法是按需定义的,可能无法兼顾没有考虑到的情况。这意味者出现新的情桨叶时,就可以需要提供新的update方法。或干脆重新实现观察者。而拉模式不会造成这种情况,因为update方法的参数是目标对象本身。这基本上是目标对象能传递的最大数据集合,基本可以适应各种情况的需要。
【编程实验】模拟事件监听系统(推模型)
//行为型模式——观察者模式 //场景——模拟事件监听系统(推模型) //Switch:事件源(开关),相当于具体的Subject角色 //EventListener:事件监听接口,相当于Observer角色 //Light:监听者,相当于具体的Observer角色 //SwitchEvent:事件对象,用于当事件发生时向监听者发送的数据类型(含事件源对象,源的状态等)。 #include <iostream> #include <string> #include <list> using namespace std; class Switch; //前向声明 //******************************辅助类(用于事件源向监听者传递的数据类型)******************** typedef void Object; //事件类(主要用来记录触发事件的源对象) class EventObject { Object* source; //记录事件源对象 public: EventObject(Object* source) { this->source = source; } Object* EventSource() { return source; } }; //开关事件,用于向监听者发生的数据 class SwitchEvent: public EventObject { string switchState; //表示开关的状态 public: SwitchEvent(Switch* source, string switchState):EventObject(source) { this->switchState = switchState; } void setSwitchState(string switchState) { this->switchState = switchState; } string& getSwitchState() { return switchState; } }; //***************************************Observer(观察者)*************************** class EventListener { public: virtual void handleEvent(SwitchEvent* switchEvent) = 0; }; //具体的监听者(相当于具体观察者) class Light : public EventListener { public: void handleEvent(SwitchEvent* switchEvent) { cout <<"the light receive a switch(" << switchEvent->EventSource() <<") emit \\""<<switchEvent->getSwitchState() <<"\\" signal"<< endl; } }; //****************************************Subject(被观察者)*********************** //抽象目标对象类 class Subject { list<EventListener*> switchListeners; public: void addListener(EventListener* listener) { switchListeners.push_back(listener); } void removeListener(EventListener* listener) { switchListeners.remove(listener); } void notifyListeners(SwitchEvent* switchEvent) { list<EventListener*>::iterator iter = switchListeners.begin(); while(iter != switchListeners.end()) { (*iter)->handleEvent(switchEvent); ++iter; } } }; //具体的目标对象,电源开关(事件源对象) class Switch : public Subject { SwitchEvent* switchEvent; public: Switch() { switchEvent = new SwitchEvent(this, "close"); } void open() { switchEvent->setSwitchState("open"); notifyListeners(switchEvent); } void close() { switchEvent->setSwitchState("close"); notifyListeners(switchEvent); } ~Switch() { delete switchEvent; } }; int main() { Switch sw; //开关,被监听对象 Light lg; //灯,监听器 sw.addListener(&lg); //加入开关的监听队列 //打开Switch sw.open(); //关闭Switch sw.close(); return 0; } /* the light receive a switch(0x23fea4) emit "open" signal the light receive a switch(0x23fea4) emit "close" signal */
4. 观察者模式的优缺点
(1)优点
①观察者模式实现了观察者和目标之间的抽象耦合
②实现了动态联动。由于观察者模式对观察者的注册实行管理,那就可以在运行期间,通过动态地控制注册的观察者,来控制某个动作的联动范围,从而实现动态联动。
③支持广播通信
(2)缺点
可能会引起无谓的操作。由于观察者模式每次都是广播通信,不管观察者需不需要,每个观察者都会被调用update方法。如果观察者不需要执行相应处理,那这次操作就浪费了,甚至可能会误操作。如本应在执行这次状态更新前把某个观察者删除掉,但现在这个观察者都还没删除,消息就又到达了,那么就会引起误操作。
5. 观察者模式的应用场景
(1)聊天室程序,服务器转发给所有客户端,群发消息等
(2)网络游戏(多人联机对战)场景中,服务器将客户端的状态进行分发。
(3)事件处理模型,基于观察者模式的委派事件模型(事件源:目标对象;事件监听器:观察者)
【编程实验】区别对待观察者(变式观察者模式)
//行为型模式——观察者模式 //场景:水质监测系统(拉模型) /*、 说明: 1、水质正常时:只通知监测人员做记录 2、轻度污染时:除了通知监测人员做记录外,还要通知预警人员,判断是否需要预警 3、中度或重度污染时:除了通知以上两人种外,还要通知部门领导做相应的处理 解决方式: 1、每次污染时,目标可以通知所有观察者,由观察者决定是否属自己处理的情况 2、每次污染时,在目标里进行判断,然后只通知相应的观察者(本例采用这种方式) */ #include <iostream> #include <string> #include <list> using namespace std; class WatcherObserver; //前向声明 //定义水质监测的目标对象 class WaterQualitySubject { protected: list<WatcherObserver*> obs; public: void attach(WatcherObserver* observer) { obs.push_back(observer); } void detach(WatcherObserver* observer) { obs.remove(observer); } //通知相应的观察者对象(这里为抽象方法,由子类去实现区别对象观察者) virtual void notifyWatchers() = 0; //获取水质污染的级别 virtual int getPolluteLevel() = 0; }; //水质观察者接口定义 class WatcherObserver { public: //被通知时的处理方法,参数为被观察的目标对象 virtual void update(WaterQualitySubject* subject) = 0; //设置和获取观察人员的职务 virtual void setJob(string job) = 0; virtual string& getJob() = 0; }; //具体的观察者 class Watcher : public WatcherObserver { private: string job; public: void setJob(string job){this->job = job;} string& getJob(){return job;} //收到通知时的处理过程 void update(WaterQualitySubject* subject) //拉模型 { cout <<job << "获取到通知,当前污染级别为:" << subject->getPolluteLevel() << endl; } }; //具体的水质监测对象 class WaterQuality : public WaterQualitySubject { private: int polluteLevel; //0正常,1轻度污染,2中度污染,3重度污染 public: WatcherQuality(){polluteLevel = 0;} int getPolluteLevel() { return polluteLevel; } void setPolluteLevel(int value) { polluteLevel = value; notifyWatchers(); //通知相应的观察者 } //通知相应的观察者对象 void notifyWatchers() { list<WatcherObserver*>::iterator iter = obs.begin(); while (iter != obs.end()) { //根据污染级别判断是否需要通知 //通知监测员记录 if(polluteLevel >=0) { if((*iter)->getJob()=="监测人员") { (*iter)->update(this); } } //通知预警人员 if(polluteLevel >=1) { if((*iter)->getJob()=="预警人员") { (*iter)->update(this); } } //通知监测部门领导 if(polluteLevel >=2) { if((*iter)->getJob()=="监测部门领导") { (*iter)->update(this); } } ++iter; } } }; int main() { //创建水质主题对象 WaterQuality subject; //创建几个观察者 WatcherObserver* watcher1 = new Watcher(); watcher1->setJob("监测人员"); WatcherObserver* watcher2 = new Watcher(); watcher2->setJob("预警人员"); WatcherObserver* watcher3 = new Watcher(); watcher3->setJob("监测部门领导"); //注册观察者 subject.attach(watcher1); subject.attach(watcher2); subject.attach(watcher3); //填写水质报告 cout << "当水质正常的时候---------------------------" << endl; subject.setPolluteLevel(0); cout << endl; cout << "当水质轻度污染的时候-----------------------" << endl; subject.setPolluteLevel(1); cout << endl; cout << "当水质中度污染的时候-----------------------" << endl; subject.setPolluteLevel(2); delete watcher1; delete watcher2; delete watcher3; return 0; } /*输出结果: 当水质正常的时候--------------------------- 监测人员获取到通知,当前污染级别为:0 当水质轻度污染的时候----------------------- 监测人员获取到通知,当前污染级别为:1 预警人员获取到通知,当前污染级别为:1 当水质中度污染的时候----------------------- 监测人员获取到通知,当前污染级别为:2 预警人员获取到通知,当前污染级别为:2 监测部门领导获取到通知,当前污染级别为:2 */
6. 相关模式
(1)观察者与状态模式
①这两者有相似之处。观察者模式是当目标状态发生改变时,触发并通知观察者,让观察者去执行相应的操作。而状态模式是根据不同的状态,选择不同的实现,这个实现类的主要功能是针对状态进行相应的操作,它不像观察者,观察者本身还有很多其他的功能,接收通知并执行相应处理只是观察者的部分功能。
②这两者可以结合使用。观察者模式的重心在触发联动,但到底决定哪些观察者会被联动,这里可以采用状态模式来实现,也可以使用策略模式来选择需要联动的观察者。
(2)观察者与中介者模式
如把一个界面所有的事件用一个中介者对象封装处理,当一个组件触发事件以后,只需要通知中介者,由于中介者封装了需要操作其他组件的动作。这样就可以实现目标对象与观察者之间的联动。
以上是关于第21章 行为型模式—观察者模式的主要内容,如果未能解决你的问题,请参考以下文章