设计模式-装饰器代理观察者

news2024/9/23 7:25:54

3.7 装饰器模式(代码见vs)

装饰器又叫做包装模式,允许向一个现有的对象添加新的功能,同时又不改变其结构。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法完整性的前提下,提供了额外的功能。

如下图:装饰器类ConDecorator想给汽车类增加新的功能,并且不能改变原来的代码,怎么做?

可以把Car基类作为装饰器类的属性,然后通过继承重写Car的原来的show方法,并且在装饰器的show方法内部,通过属性Car类对象(这个对象传过来的是一个具体的子类对象,比如Audi或者Bmw)调用具体子类对象的show方法,在这个方法基础上,增加新的方法和功能。这样就实现了不改变原来代码的基础上,增加了新的功能。然后我们通过使用装饰器对象,就可以使用装饰后的新功能。

装饰器模式,既有继承又有合成关系。

#include <iostream>
using namespace std;

//装饰器模式
class Car
{
public:
	virtual void show() = 0;
};
//具体车型:奥迪
class Audi :public Car
{
public:
	virtual void show() { cout << "这是一辆奥迪车,标配。"; }
};
//具体车型:宝马
class Bmw :public Car
{
public:
	virtual void show() { cout << "这是一辆宝马车,标配。"; }
};
//第一个装饰器,加上定速巡航
class ConDecorator01 :public Car
{
	Car* car;
public:
	ConDecorator01(Car* c) :car(c) {};
	void show()
	{
		//先调用原来的show方法,通过属性car来调用
		car->show();
		//在原来的基础上加装饰,装定速巡航
		cout << "装饰了定速巡航" << endl;
	}
};
//第二个装饰器,加上自动刹车
class ConDecorator02 :public Car
{
	Car* car;
public:
	ConDecorator02(Car* c) :car(c) {};
	void show()
	{
		//先调用原来的show方法,通过属性car来调用
		car->show();
		//在原来的基础上加装饰,装自动刹车
		cout << "装饰了自动刹车" << endl;
	}
};
//第三个装饰器,加上自动泊车
class ConDecorator03 :public Car
{
	Car* car;
public:
	ConDecorator03(Car* c) :car(c) {};
	void show()
	{
		//先调用原来的show方法,通过属性car来调用
		car->show();
		//在原来的基础上加装饰,装自动泊车
		cout << "装饰了自动泊车" << endl;
	}
};
void test01()
{
	Car* c1 = new Bmw();//标配的宝马
	c1->show();
	cout << endl;
	Car* d1 = new ConDecorator01(c1);
	d1->show();//此时有了定速巡航功能

	Car* c2 = new Audi();//标配的奥迪
	c2->show();
	cout << endl;
	Car* d2 = new ConDecorator02(c2);
	d2->show();//此时有了自动刹车功能

	delete c1;
	delete c2;
	delete d1;
	delete d2;
}

装饰器模式练习:

大家知不知道QQ秀这个游戏,80后应该知道, 给动画人物搭配不同服饰。比如穿T恤,衬衫,外套,皮鞋,运动鞋,靴子...,根据下面的类图完成这个练习。

注意:这个练习跟上面的汽车例子不同,汽车例子是车有抽象层和具体层的类,装饰器只有一层,每个装饰器直接实现装饰。这个作业是被装饰的人只有一层,装饰器有两层,抽象层定义接口,不负责具体的装饰,具体装饰由具体层的装饰器完成。

//未装饰的人
class Person
{
	string name;
public:
	Person() {};//无参构造需要有,因为子类构造的时候要用
	Person(string na) :name(na) {};
	virtual void show() { cout << "我是" << name << endl; }
};
//装饰类父类,抽象类
class Finery:public Person
{
protected:
	Person* per;
public:
	Finery(Person* p) :per(p) {};//这里用到了Person的无参构造
	virtual void show() = 0;
};
//具体装饰:长裤
class LongTrouser :public Finery
{
public:
	LongTrouser(Person* p) :Finery(p) {};
	void show()
	{
		per->show();//调用原来未装饰的show方法
		//接下来加装饰
		cout << "穿上长裤" << endl;
	}
};
//具体装饰:T恤
class Tshirts :public Finery
{
public:
	Tshirts(Person* p) :Finery(p) {};
	void show()
	{
		per->show();//调用原来未装饰的show方法
		//接下来加装饰
		cout << "穿上T恤" << endl;
	}
};
void test02()
{
	Person* xm = new Person("小明");
	xm->show();//没装饰
	cout << "装饰后:" << endl;
	Finery* ts_xm = new Tshirts(xm);//穿上T恤
	ts_xm->show();
	Finery* lt_xm = new LongTrouser(xm);//穿上长裤
	lt_xm->show();
	delete xm;
	delete ts_xm;
	delete lt_xm;
}

3.8 代理模式(代码见vs)

代理模式也称为委托模式,作用就是为其他对象提供一种代理以控制对这个对象的访问。它允许你在不直接访问对象的情况下,通过一个代理对象来控制对该对象的访问。这个代理对象可以作为客户端和实际对象之间的中介,从而实现一些特定的控制功能,比如限制访问、记录访问日志等。

代理模式和装饰器模式很像:

1)相同点:都是继承了目标抽象类,都将目标抽象类关联到本类中作为属性。

2)代理强调的是对目标对象的控制权(强迫用户使用代理,不用就无法访问目标对象);装饰器模式强调的是在不修改源代码的基础上添加新的功能。

//代理模式
//抽象层,房东
class Landlord
{
public:
	virtual void rentHouse() = 0;
};
//具体的房东:Tom
class Tom :public Landlord
{
public:
	virtual void rentHouse() { cout << "Tom出租一套房子" << endl; }
};
//代理类
class Proxy :public Landlord
{
	Landlord* landlord;
public:
	Proxy(Landlord* land):landlord(land){}
	//接下来对房东出租房子的行为加限制,必须给中介交钱后,才能出租房子
	virtual void rentHouse()
	{
		cout << "中介先收取佣金" << endl;
		landlord->rentHouse();//然后才可以使用出租房子的方法
	}
};
void test03()
{
	Landlord* tom = new Tom();
	Landlord* proxy = new Proxy(tom);//中介代理了tom的房子
	proxy->rentHouse();//通过中介租房子,必须先交钱
	delete tom;
	delete proxy;
}

总结:

优点:职责清晰:真实角色就是实现实际的业务逻辑,不关心其他非本职的事务,通过后期的代理完成非本质事务,编程简单清晰。 高扩展性:具体主题角色可变。

缺点:由于在客户端和真实主题之间增加了代理,因此可能会造成请求的处理速度变慢(因为代理加了控制)。实现代理模式需要额外的工作,有些实现非常复杂。

代理模式练习:

//送礼者抽象类,某个人
class SomeOne
{
public:
	virtual void giveFlowers() = 0;
	virtual void giveDolls() = 0;
	virtual void giveChoc() = 0;
};
//具体送礼的人
class One :public SomeOne
{
	string name;
public:
	One(string n) :name(n) {};
	virtual void giveFlowers() { cout << name << "送您鲜花" << endl; }
	virtual void giveDolls(){ cout << name << "送您洋娃娃" << endl; }
	virtual void giveChoc(){ cout << name << "送您巧克力" << endl; }
};
//代理类
class Proxy_for :public SomeOne
{
	SomeOne* m_one;
public:
	Proxy_for(SomeOne* one) :m_one(one) {};
	virtual void giveFlowers() { cout << "送鲜花需要收取佣金100" << endl; m_one->giveFlowers(); }
	virtual void giveDolls() { cout << "送洋娃娃需要收取佣金150" << endl; m_one->giveDolls(); }
	virtual void giveChoc() { cout << "送巧克力需要收取佣金80" << endl; m_one->giveChoc(); }
};
void test04()
{
	SomeOne* jerry = new One("jerry");
	SomeOne* p = new Proxy_for(jerry);
	p->giveFlowers();
	p->giveDolls();
	p->giveChoc();
	delete jerry;
	delete p;
}

3.9 观察者模式(代码见vs)

观察者模式又叫做发布-订阅模式,定义了一种一对多的依赖关系,一对多,一是发布者,多是订阅者。让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动的更新。观察者往往定义一个抽象观察者,多个具体观察者。

被观察主题对象应该包含一个容器来存放观察者对象,当被观察者发生改变时通知容器内所有的观察者对象。这样才能实现一对多。

观察者对象加入到主题的容器中,相当于订阅了主题,然后就可以接收被观察者的通知。观察者也可以被删除掉,停止订阅这个主题。

需求:主题作为发布者群发消息,多个观察者订阅主题,主题有消息时通知所有观察者。

//观察者模式
//抽象观察者
class AbsObserver
{
public:
	virtual void update(string content) = 0;//更新的接口,参数content是更新的内容
};
//主题类,发布者
class Subject
{
	string title;//标题
	list<AbsObserver*> obs;//容器中存放抽象观察者的地址
public:
	Subject(string t):title(t){}
	~Subject()
	{
		//发布者析构的时候,需要将全部的订阅者析构掉
		if (obs.size()==0)
		{
			return;
		}
		//如果容器中有订阅者,逐个回收
		for (auto o:obs)
		{
			delete o;//回收每个观察者对象
			obs.remove(o);//将观察者对象地址从链表中移除
		}
	}
	void attach(AbsObserver* someone)//绑定,即将某个观察者加入订阅,加入容器中
	{
		obs.push_back(someone);
	}
	void detach(AbsObserver* someone)//解绑,将某个观察者解除订阅,从容器中移除
	{
		obs.remove(someone);
		delete someone;
	}
	string getTitle() { return title; }
	void notify(string content)//通知,将content通知给订阅者
	{
		if (obs.size()==0)
		{
			return;
		}
		for (auto o:obs)//逐个通知容器内的订阅者
		{
			o->update(content);//将content传递给订阅者
		}
	}
};
//具体观察者
class ConsObserver :public AbsObserver
{
	string name;//观察者姓名
	Subject* subject;//订阅的主题
public:
	ConsObserver(string n, Subject* s) :subject(s), name(n) {};
	void update(string content)//将发布者传过来的content内容进行展示
	{
		cout << "标题:[" << subject->getTitle() << "],内容:" << content << "," << name << "已收到" << endl;
	}
};
void test05()
{
	//准备主题和观察者对象
	Subject* subject = new Subject("天气预报");
	AbsObserver* ob1 = new ConsObserver("观察者1号", subject);
	AbsObserver* ob2 = new ConsObserver("观察者2号", subject);
	//加入订阅
	subject->attach(ob1);
	subject->attach(ob2);
	//发布通知
	subject->notify("最近天气炎热并且伴有大风");
	//解除订阅
	subject->detach(ob2);
	//再次通知,此时ob2就收不到消息了
	subject->notify("中秋节期间天气很好,大家可以轻松出行");
	//回收的时候,只需要回收主题对象即可,主题的析构中回收了所有订阅者
	delete subject;
}

观察者模式总结

观察者模式的优势:主题(Subject)无需耦合某个具体的观察者(如ConsObserver),而只需要知道其抽象接口AbsObserver即可。观察者模式解除了主题和具体观察者的耦合,依赖于抽象,而不是依赖具体。从而使得观察者的变化不会影响主题。

观察者模式的缺点:性能损耗,即在函数调用前遍历观察者列表的开销。

应用场景:通知,群发的场景。

注意:在销毁观察者对象前,必须取消订阅此观察者对象,否则通知一个已销毁的观察者可能导致程序崩溃。

观察者模式练习:

在此基础上,改造主题,主题是默认的新闻主页,主题下面还有具体的频道:经济、体育、娱乐(选择一个即可)。这样主题也分为两个层,观察者可以订阅新闻主页,也可以订阅具体的频道。

//抽象观察者
class Observer
{
public:
	virtual void update(string content) = 0;//更新的接口,参数content是更新的内容
};
//主题类,发布者,新闻主页
class MainSubject
{
	string title;//标题
	list<Observer*> obs;//容器中存放抽象观察者的地址
public:
	MainSubject(){}
	MainSubject(string t) :title(t) {}
	virtual ~MainSubject()
	{
		//发布者析构的时候,需要将全部的订阅者析构掉
		if (obs.size() == 0)
		{
			return;
		}
		//如果容器中有订阅者,逐个回收
		for (auto o : obs)
		{
			delete o;//回收每个观察者对象
			obs.remove(o);//将观察者对象地址从链表中移除
		}
	}
	virtual void attach(Observer* someone)//绑定,即将某个观察者加入订阅,加入容器中
	{
		obs.push_back(someone);
	}
	virtual void detach(Observer* someone)//解绑,将某个观察者解除订阅,从容器中移除
	{
		obs.remove(someone);
		delete someone;
	}
	virtual string getTitle() { return title; }//需要写成虚函数,子类才可以实现多态
	virtual void notify(string content)//通知,将content通知给订阅者
	{
		if (obs.size() == 0)
		{
			return;
		}
		for (auto o : obs)//逐个通知容器内的订阅者
		{
			o->update(content);//将content传递给订阅者
		}
	}
};
//具体的主题:经济主题
class Subject_jingji :public MainSubject
{
	string title;//标题
	list<Observer*> obs;//容器中存放抽象观察者的地址
public:
	Subject_jingji(string t) :title(t) {}
	~Subject_jingji()
	{
		//发布者析构的时候,需要将全部的订阅者析构掉
		if (obs.size() == 0)
		{
			return;
		}
		//如果容器中有订阅者,逐个回收
		for (auto o : obs)
		{
			delete o;//回收每个观察者对象
			obs.remove(o);//将观察者对象地址从链表中移除
		}
	}
	void attach(Observer* someone)//绑定,即将某个观察者加入订阅,加入容器中
	{
		obs.push_back(someone);
	}
	void detach(Observer* someone)//解绑,将某个观察者解除订阅,从容器中移除
	{
		obs.remove(someone);
		delete someone;
	}
	string getTitle() { return title; }
	void notify(string content)//通知,将content通知给订阅者
	{
		if (obs.size() == 0)
		{
			return;
		}
		for (auto o : obs)//逐个通知容器内的订阅者
		{
			o->update(content);//将content传递给订阅者
		}
	}
};
//具体观察者
class ConcreteObserver :public Observer
{
	string name;//观察者姓名
	MainSubject* subject;//订阅的主题
public:
	ConcreteObserver(string n, MainSubject* s) :subject(s), name(n) {};
	void update(string content)//将发布者传过来的content内容进行展示
	{
		cout << "标题:[" << subject->getTitle() << "],内容:" << content << "," << name << "已收到" << endl;
	}
};
void test06()
{
	MainSubject* sub = new MainSubject("今日新闻");//首页
	MainSubject* sub_jingji = new Subject_jingji("经济新闻");//经济频道主题
	Observer* ob1 = new ConcreteObserver("观察者1号", sub);//观察者1号订阅了首页
	Observer* ob2 = new ConcreteObserver("观察者2号", sub_jingji);//观察者2号订阅了经济
	Observer* ob3 = new ConcreteObserver("观察者3号", sub_jingji);//观察者3号订阅了经济
	//加入订阅
	sub->attach(ob1);
	sub_jingji->attach(ob2);
	sub_jingji->attach(ob3);
	//发布信息
	sub->notify("各类新闻汇聚于此");
	sub_jingji->notify("中指研究院发布了2024年1-8月份全国房价走势,同比和环比均下架");
	//取消订阅
	sub_jingji->detach(ob2);
	sub_jingji->notify("中国汽车出口份额世界第一");
	delete sub;
	delete sub_jingji;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2105673.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Android Studio的行程记录APK开发指南(二):熟悉一个项目结构

前言 最近博主在unity开发独立游戏&#xff0c;UE5系列的相关长期教程先暂时不更新了,请大家多多谅解本系列教程我们来看看如何使用Android Studio去开发一个APK用于用户的实时行程记录 第一期&#xff1a;基于Android Studio的用户行程记录APK开发指南(一)&#xff1a;项目基…

CTF---密码学知识点总结

✨Ascall编码&#xff1a;在 ctf 比赛中&#xff0c;flag 的标志一般是以 Ascall 码的形式存在&#xff0c;其对应的码值为102&#xff0c;108&#xff0c;97&#xff0c;103&#xff08;其中{的码值是123&#xff09;&#xff01; ✨Unicode编码&#xff1a;又名万国码&#…

OpenHarmony持久化存储UI状态:PersistentStorage

前两个小节介绍的LocalStorage和AppStorage都是运行时的内存&#xff0c;但是在应用退出再次启动后&#xff0c;依然能保存选定的结果&#xff0c;是应用开发中十分常见的现象&#xff0c;这就需要用到PersistentStorage。 PersistentStorage是应用程序中的可选单例对象。此对…

海外云服务器安装 MariaDB10.6.X (Ubuntu 18.04 记录篇二)

本文首发于 秋码记录 MariaDB 的由来&#xff08;历史&#xff09; 谈起新秀MariaDB&#xff0c;或许很多人都会感到陌生吧&#xff0c;但若聊起享誉开源界、业界知名的关系型数据库——Mysql&#xff0c;想必混迹于互联网的人们&#xff08;coder&#xff09;无不知晓。 其…

C++中protobuffer的具体使用方法以及重要原理的实现

一、protobuffer的具体使用 对于基本的知识可以看我之前的文章。 那一片文章主要是知识点&#xff0c;这一片是实战。 1、头部 我们通过syntax 这个来指定版本号&#xff0c;如果不写的话就会默认为proto2&#xff0c;2这个版本是一个比较旧的版本。旧的版本写起来就比较繁琐。…

地平线Sparse4D论文解析(含论文原文)

0. 摘要 在自动驾驶感知系统中&#xff0c;3D 检测和跟踪是两个基本任务。本文深入研究了这一领域&#xff0c;并在 Sparse4D 框架的基础上进行了扩展。我们引入了两个辅助训练任务&#xff08;时间实例去噪和质量估计&#xff09;&#xff0c;并提出了解耦注意力机制&#xf…

智能计算方法与实现2|模拟退火算法原理|工具箱及其应用

模拟退火算法原理 模拟退火算法 模拟退火算法&#xff08;SimulatedAnnealing&#xff0c;SA&#xff09;最早的思想是由N.Metropolis等人于1953年提出。 1983年&#xff0c;S.Kirkpatrick等成功地将退火思想引l入到组合优化领域 它是基于Monte-Carlo送代求解策略的一种随机寻…

MATLAB 仿真跳频扩频通信系统

1. 简介 跳频扩频&#xff08;FHSS&#xff09;是一种通过在不同的频率之间快速切换来对抗窄带干扰的技术。在这篇博客中&#xff0c;我们将使用 MATLAB 进行 FHSS 通信系统的仿真&#xff0c;模拟跳频过程、调制、解调以及信号在不同步骤中的变化。通过对仿真结果进行可视化&…

为虚拟机配置固定的IP地址(CentOS9)

配置虚拟网卡 首先关闭虚拟机 打开虚拟网络编辑器 选择更改配置 选择VMnet8&#xff0c;选择子网的IP和掩码 &#xff08;这里的子网掩码为255.255.255.0&#xff0c;表示前24位为网络号&#xff0c;后8位为主机号&#xff09;然后点击DHCP设置 设置开始IP地址和结束IP地址&…

远端ide ,vscode ,python 开发环境, 有些还有一建生成chatgpt功能,支持gpu功能

现在智能化的AI工具&#xff0c;可以实现智能聊天、文本生成、语言翻译等多种功能。 博主归纳总结了6个好用免费的AI工具网站&#xff0c;供大家参考。 ## 1&#xff0c;insCode 网址&#xff1a; https://inscode.csdn.net/ 简介&#xff1a; InsCode 是一个以“灵感”&am…

【知识点】图论续篇 - 最短路算法合集

我不会跟大家说我两个月前就写好了&#xff0c;只是今天才发出来。 本文概述 最短路算法&#xff0c;见名知意&#xff0c;就是用于求出图中从某个顶点到另一个顶点最短距离的算法。最短路算法的应用极其广泛。本文将会以求解最短路为中心&#xff0c;围绕着展开叙述一些常见的…

高清无损!探索PDF转JPG的最佳实践工具

在信息爆炸的今天&#xff0c;PDF文件因其跨平台兼容性和文档保护特性&#xff0c;成为了工作、学习和日常生活中不可或缺的一部分。但是很多时候我们并不需要精度那么高的文件&#xff0c;图片分享更符合快捷的要求。这次我们就一起探讨有什么PDF转jpg的工具吧。 1.福昕PDF转…

SignalR——聊天室实践

SignalR 是一个为 ASP.NET 开发者设计的库&#xff0c;它简化了在 Web 应用程序中添加实时功能的过程。实时功能指的是服务器能够在客户端没有发起请求的情况下主动向客户端推送内容的能力。这种技术使得服务器和客户端之间的通信更加动态和即时&#xff0c;非常适合需要实时更…

iOS P8证书推送测试

最近在配合服务端人员调试相关的 APNS auth key 推送的问题&#xff0c;相比于苹果的P12证书的推送&#xff0c;P8证书的推送显得方便很多&#xff0c;P8的优势在于简单&#xff0c;安全 容易生成 最重要的是不会过期。 现在我们来看下测试具体流程&#xff1a; 方法一 地址…

Hive服务部署及Datagrip工具使用

目录 Hive服务部署 Hiveserver2服务 1&#xff09;用户说明 2&#xff09;Hiveserver2部署 &#xff08;1&#xff09;Hadoop端配置 &#xff08;2&#xff09;Hive端配置 3&#xff09;测试 &#xff08;1&#xff09;启动Hiveserver2 &#xff08;2&#xff09;使用命…

GoFly企业版里的阿里图标如何增加自定义图标到后台

1.在使用的vue页面引入图标组件 <script lang"ts" setup>import {Icon} from /components/Icon;</script> 2.在具体位置使用 <template><Icon icon"svgfont-icon7" class"iconbtn" :size"18" color"#ed6…

如何进行 AWS 云监控

什么是 AWS&#xff1f; Amazon Web Services&#xff08;AWS&#xff09;是 Amazon 提供的一个全面、广泛使用的云计算平台。它提供广泛的云服务&#xff0c;包括计算能力、存储选项、网络功能、数据库、分析、机器学习、人工智能、物联网和安全。 使用 AWS 有哪些好处&…

华为云全栈可观测平台(APM)8月新功能特性

华为云应用性能管理服务&#xff08;Application Performance Management&#xff0c;简称APM&#xff09;帮助运维人员快速发现应用的性能瓶颈&#xff0c;以及故障根源的快速定位&#xff0c;为用户体验保驾护航。 您无需修改代码&#xff0c;只需为应用安装一个APM Agent&a…

Linux/Ubuntu服务器 screen 安装与使用

一、screen简单介绍 在Linux系统中&#xff0c;screen是一个非常强大的终端仿真器&#xff0c;它允许用户在一个终端窗口中创建多个子窗口&#xff0c;每个子窗口都可以运行一个独立的会话。screen的主要特点包括&#xff1a; 会话分离&#xff1a;screen允许用户在终端会话中运…

lottie-web动画库实战详解

安装 npm install lottie-web pnpm install lottie-web yarn add lottie-web <divid"animation"style"width: 700px; height: 440px; margin-top: 80px"></div>import lottie from "lottie-web"; import loginJson from ".…