面向对象设计模式--观察者模式(C++可执行代码)

观察者模式

(Observer)

        观察者 是一种行为设计模式,允许你定义一种订阅机制,可在对象事件发生时通知多个“观察”该对象的其他对象。

1. 问题 

        假如你有两种类型的对象: 顾客 和 商店 。顾客对某个特定品牌的产品非常感兴趣(例如最新型号的 iPhone 手机),而该产品很快将会在商店里出售。

        顾客可以每天来商店看看产品是否到货。但如果商品尚未到货时,绝大多数来到商店的顾客都会空手而归。

前往商店和发送垃圾邮件

        另一方面,每次新产品到货时,商店可以向所有顾客发送邮件(可能会被视为垃圾邮件)。这样,部分顾客就无需反复前往商店了,但也可能会惹恼对新产品没有兴趣的其他顾客。

        我们似乎遇到了一个矛盾:要么让顾客浪费时间检查产品是否到货,要么让商店浪费资源去通知没有需求的顾客。

2. 解决方案

        拥有一些值得关注的状态的对象通常被称为目标,由于它要将自身的状态改变通知给其他对象,我们也将其称为发布者(publisher)。所有希望关注发布者状态变化的其他对象被称为订阅者(subscribers)。

        观察者模式建议你为发布者类添加订阅机制,让每个对象都能订阅或取消订阅发布者事件流。不要害怕!这并不像听上去那么复杂。实际上,该机制包括 1)一个用于存储订阅者对象引用的列表成员变量;2)几个用于添加或删除该列表中订阅者的公有方法。

        现在,无论何时发生了重要的发布者事件,它都要遍历订阅者并调用其对象的特定通知方法。

        实际应用中可能会有十几个不同的订阅者类跟踪着同一个发布者类的事件, 你不会希望发布者与所有这些类相耦合的。

订阅机制允许对象订阅事件通知。

        此外如果他人会使用发布者类,那么你甚至可能会对其中的一些类一无所知。

        因此,所有订阅者都必须实现同样的接口,发布者仅通过该接口与订阅者交互。接口中必须声明通知方法及其参数,这样发布者在发出通知时还能传递一些上下文数据。

发布者调用订阅者对象中的特定通知方法来通知订阅者。

        如果你的应用中有多个不同类型的发布者,且希望订阅者可兼容所有发布者,那么你甚至可以进一步让所有订阅者遵循同样的接口。该接口仅需描述几个订阅方法即可。这样订阅者就能在不与具体发布者类耦合的情况下通过接口观察发布者的状态。

3. 结构

观察者模式的结构

        其中:

        *Subject(目标) 知道它的观察者, 可以有任意多个观察者观察同一个目标; 提供注册和删除观察者对象的接口。

        *Observer(观察者) 为那些在目标发生改变时需获得通知的对象定义一个更新接口。

        *Concrete Subject(具体目标) 将有关状态存入各Concrete Observer对象; 当它的状态发生改变时,向它的各个观察者发出通知。

        *Concrete Observer(具体观察者) 维护一个指向Concrete Subject对象的引用; 存储有关状态, 这些状态应与目标的状态保持一致; 实现Observer的更新接口, 以使自身状态与目标的状态保持一致。

4. 实现方式

1. 仔细检查你的业务逻辑,试着将其拆分为两个部分:

        独立于其他代码的核心功能将作为发布者;其他代码则将转化为一组订阅类。

2. 声明订阅者接口。

        该接口至少应声明一个 update 方法。

3.  声明发布者接口并定义一些接口来在列表中添加和删除订阅对象。

        记住发布者必须仅通过订阅者接口与它们进行交互。

4. 确定存放实际订阅列表的位置并实现订阅方法。

        通常所有类型的发布者代码看上去都一样,因此将列表放置在直接扩展自发布者接口的抽象类中是显而易见的。具体发布者会扩展该类从而继承所有的订阅行为。

        但是,如果你需要在现有的类层次结构中应用该模式,则可以考虑使用组合的方式: 将订阅逻辑放入一个独立的对象,然后让所有实际订阅者使用该对象。

5. 创建具体发布者类。

        每次发布者发生了重要事件时都必须通知所有的订阅者。

6. 在具体订阅者类中实现通知更新的方法。

        绝大部分订阅者需要一些与事件相关的上下文数据。这些数据可作为通知方法的参数来传递。但还有另一种选择。订阅者接收到通知后直接从通知中获取所有数据。在这种情况下,发布者必须通过更新方法将自身传递出去。另一种不太灵活的方式是通过构造函数将发布者与订阅者永久性地连接起来。

7. 客户端必须生成所需的全部订阅者,并在相应的发布者处完成注册工作。

5. 代码示例

observer.h

#ifndef DESIGN_PATTERNS_OBSERVER_H
#define DESIGN_PATTERNS_OBSERVER_H

#include <iostream>
#include <string>
#include <vector>
using namespace std;
//------------------------------//
class Notifier;//"通知器"类
//------------------------------//
class Observer //"观察者"类
{
public:
	Observer() {}
	Observer(string);
	virtual ~Observer() {}
	void SetNotifier(Notifier *);
	virtual void Update() = 0;

protected:
	string name_;
	Notifier *notifier_;
};

class StockObserver : public Observer //"资本观察"类
{
public:
	StockObserver() {}
	StockObserver(string);
	void Update();
};

class NbaObserver : public Observer //"NBA观察"类
{
public:
	NbaObserver() {}
	NbaObserver(string);
	void Update();
};
//------------------------------//
class Notifier 
{
public:
	virtual ~Notifier() {}
	void Attach(Observer *);//加载
	void Detach(Observer *);//卸载
	void SetState(string);//设置状态
	string GetState();//获取状态
	void Notify();//通知

protected:
	vector <Observer*> observers_;
	string state_;
};

class Secretary : public Notifier //"秘书"类
{
};

class Boss : public Notifier //"老板"类
{
};
//------------------------------//
#endif //DESIGN_PATTERNS_OBSERVER_H

observer.c

#include "observer.h"
//------------------------------//
Observer::Observer(string name) : name_(name) {}

void Observer::SetNotifier(Notifier *notifier) 
{
	notifier_ = notifier;
}
//------------------------------//
void Notifier::Attach(Observer * observer) 
{
	observers_.push_back(observer);
}

void Notifier::Detach(Observer * observer) 
{
	for (vector <Observer*> ::iterator it = observers_.begin(); it != observers_.end(); ++it) 
	{
		if (*it == observer) 
		{
			observers_.erase(it);
			return;
		}
	}
}

void Notifier::SetState(string state) 
{
	state_ = state;
}

string Notifier::GetState() 
{
	return state_;
}

void Notifier::Notify() 
{
	for (vector <Observer*> ::iterator it = observers_.begin(); it != observers_.end(); ++it) 
	{
		(*it)->Update();
	}
}
//------------------------------//
StockObserver::StockObserver(string name) : Observer(name) {}

void StockObserver::Update() 
{
	cout << name_ << ", " << notifier_->GetState() << ", close stock" << endl;
}
//------------------------------//
NbaObserver::NbaObserver(string name) : Observer(name) {}

void NbaObserver::Update() 
{
	cout << name_ << ", " << notifier_->GetState() << ", close NBA" << endl;
}
//------------------------------//

Main.c

//------------------------------//
#include <iostream>
#include "observer.h"
using namespace std;
//------------------------------//
// Created by Cls on 2024/04/01.
//------------------------------//
int main(int argc, char *argv[])
{
	Boss *boss_;
	StockObserver *stock_observer_;
	NbaObserver *nba_observer_;
	//-----------------//
	boss_ = new Boss();
	stock_observer_ = new StockObserver("Alice");
	nba_observer_ = new NbaObserver("Bob");
	boss_->SetState("boss is back himself");

	stock_observer_->SetNotifier(boss_);
	nba_observer_->SetNotifier(boss_);
	
	boss_->Attach(stock_observer_);
	boss_->Attach(nba_observer_);
	
	//-----------------//
	boss_->Notify();
	cout << "------------------" << endl;
	//-----------------//
	boss_->Detach(nba_observer_);
	boss_->Notify();
	cout << "------------------" << endl;

	//-----------------//
	delete boss_;
	delete stock_observer_;
	delete nba_observer_;
	//-----------------//
	return 0;
}
//------------------------------//

打印输出

6. 应用场景

当一个对象状态的改变需要改变其他对象,或实际对象是事先未知的或动态变化的时,可使用观察者模式。

        当你使用图形用户界面类时通常会遇到一个问题。比如,你创建了自定义按钮类并允许客户端在按钮中注入自定义代码,这样当用户按下按钮时就会触发这些代码。

        观察者模式允许任何实现了订阅者接口的对象订阅发布者对象的事件通知。你可在按钮中添加订阅机制,允许客户端通过自定义订阅类注入自定义代码。

当应用中的一些对象必须观察其他对象时,可使用该模式。但仅能在有限时间内或特定情况下使用。

        订阅列表是动态的,因此订阅者可随时加入或离开该列表。

7. 优缺点

        √ 开闭原则。 你无需修改发布者代码就能引入新的订阅者类(如果是发布者接口则可轻松引入发布者类)。

        √ 你可以在运行时建立对象之间的联系。

        × 订阅者的通知顺序是随机的。

8. 与其他模式的关系

        • 责任链、命令、中介者和观察者用于处理请求发送者和接收者之间的不同连接方式:

        ◦ 责任链按照顺序将请求动态传递给一系列的潜在接收者,直至其中一名接收者对请求进行处理。

        ◦ 命令在发送者和请求者之间建立单向连接。

        ◦ 中介者清除了发送者和请求者之间的直接连接,强制它们通过一个中介对象进行间接沟通。

        ◦ 观察者允许接收者动态地订阅或取消接收请求。

        • 中介者和观察者之间的区别往往很难记住。在大部分情况下,你可以使用其中一种模式,而有时可以同时使用。让我们来看看如何做到这一点。

        中介者的主要目标是消除一系列系统组件之间的相互依赖。这些组件将依赖于同一个中介者对象。观察者的目标是在对象之间建立动态的单向连接,使得部分对象可作为其他对象的附属发挥作用。

        有一种流行的中介者模式实现方式依赖于观察者。中介者对象担当发布者的角色,其他组件则作为订阅者,可以订阅中介者的事件或取消订阅。当中介者以这种方式实现时,它可能看上去与观察者非常相似。

        当你感到疑惑时,记住可以采用其他方式来实现中介者。例如,你可永久性地将所有组件链接到同一个中介者对象。这种实现方式和观察者并不相同,但这仍是一种中介者模式。

        假设有一个程序,其所有的组件都变成了发布者,它们之间可以相互建立动态连接。这样程序中就没有中心化的中介者对象,而只有一些分布式的观察者。

  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值