C++ 设计模式 3:结构型模式
0 结构型模式
让类和类进行组合,获得更大的结构,获得新功能的方式。
1 代理模式
Proxy 模式又被叫做代理模式,是结构型的设计模式之一,它可以 为其他对象提供一种代理以控制对这个对象的访问。
所谓 代理,是指具有与代理元(被代理的对象)具有相同的接口的类,客户端必须 通过代理与被代理的目标类交互,而代理一般在交互的过程中,进行某些特别的处理。
1.1 模式中的角色和职责
subject(抽象主题角色):真实主题与代理主题的共同接口。
RealSubject(真实主题角色):定义了代理角色所代表的真实对象。
Proxy(代理主题角色):含有对真实主题角色的引用,代理角色通常在将客户端调用传递给真实主题对象之前或者之后执行某些操作,而不是单纯返回真实的对象。
1.2 案例
示例代码:
#include <iostream>
#include <string>
using namespace std;
// 物品
class Item
{
public:
Item(string kind,bool fake)
{
this->kind = kind;
this->fake = fake;
}
// 物品类别
string kind;
// true:假货,false:真货
bool fake;
};
// subject(抽象主题角色),抽象的购物方式,具有买的功能
class Shopping
{
public:
virtual void buy(Item& item) = 0;
};
// RealSubject(真实主题角色),去韩国购物
class KoreaShopping : public Shopping
{
public:
virtual void buy(Item& item)
{
cout << "去韩国购物,买了" << item.kind << endl;
}
};
// RealSubject(真实主题角色),去美国购物
class USAShopping : public Shopping
{
public:
virtual void buy(Item& item)
{
cout << "去美国购物,买了" << item.kind << endl;
}
};
// RealSubject(真实主题角色),去非洲购物
class AfricaShopping : public Shopping
{
public:
virtual void buy(Item& item)
{
cout << "去非洲购物,买了" << item.kind << endl;
}
};
// Proxy(代理主题角色),海外代购 代理,实现了购物模式,而且还增加了辨别货物真伪和海关安检具体业务。
class OverseasProxy : public Shopping
{
public:
OverseasProxy(Shopping* shopping)
{
this->shopping = shopping;
}
~OverseasProxy()
{
delete this->shopping;
}
bool distinguish(Item& item)
{
cout << "对物品[" << item.kind << "]辨别真伪." << endl;
return item.fake;
}
void check(Item& item)
{
cout << "通过海关安检,带回国内" << endl;
}
virtual void buy(Item& item)
{
if(distinguish(item) == false)
{
this->shopping->buy(item);
check(item);
}
else
{
cout << "发现伪货[" << item.kind << "],不能购买" << endl;
}
}
private:
Shopping* shopping;
};
int main(void)
{
Item item1("化妆品",false);
Item item2("学位证",true);
Item item3("空调",false);
Shopping* shopping = NULL;
OverseasProxy* proxy = NULL;
// 1.去韩国买化妆品
proxy = new OverseasProxy(new KoreaShopping);
proxy->buy(item1);
delete proxy;
// 2.去美国买学位证
proxy = new OverseasProxy(new USAShopping);
proxy->buy(item2);
delete proxy;
// 3.去非洲买空调
proxy = new OverseasProxy(new AfricaShopping);
proxy->buy(item3);
delete proxy;
return 0;
}
运行结果:
1.3 代理模式的种类
(1) 远程代理(Remote Proxy):为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又称为大使(Ambassador)
(2)虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
(3)保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
(4)缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
(5)智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等
1.4 优缺点
优点:
(1)能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
(2)客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。
缺点:
(1)代理实现较为复杂
1.5 适用场景
为其他对象提供一种代理以控制对这个对象的访问。
2 装饰模式
装饰模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式 比生成子类实现更为灵活。
2.1 模式中的角色和职责
Component(抽象构件):它是 具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。
ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。
Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。
ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。
2.2 案例
示例代码:
#include <iostream>
#include <string>
using namespace std;
// Component(抽象构件),它是具体构件和抽象装饰类的共同父类,手机
class Phone
{
public:
virtual void show() = 0;
};
// ConcreateComponent(具体构件),iphone
class Iphone : public Phone
{
public:
Iphone(string kind)
{
this->kind = kind;
}
virtual void show()
{
cout << "我掏出了 iphone-" << kind << "秀了秀" << endl;
}
private:
string kind;
};
// ConcreateComponent(具体构件),小米手机
class MiPhone : public Phone
{
public:
MiPhone(string kind)
{
this->kind = kind;
}
virtual void show()
{
cout << "我掏出了 小米-" << kind << "秀了秀" << endl;
}
private:
string kind;
};
// Decorator(抽象装饰类),手机装饰器
class DecoratorPhone : public Phone
{
public:
DecoratorPhone()
{
}
DecoratorPhone(Phone* phone)
{
this->phone = phone;
}
virtual void show()
{
this->phone->show();
}
private:
Phone* phone;
};
// ConcreteDecorator(具体装饰类),贴膜装饰器
class DecoratorPhoneMo : public DecoratorPhone
{
public:
DecoratorPhoneMo(Phone* phone)
{
this->phone = phone;
}
void AddMo()
{
cout << "装饰:手机贴膜" << endl;
}
virtual void show()
{
this->phone->show();
AddMo();
}
private:
Phone* phone;
};
// ConcreteDecorator(具体装饰类),皮套装饰器
class DecoratorPhoneTao : public DecoratorPhone
{
public:
DecoratorPhoneTao(Phone* phone)
{
this->phone = phone;
}
void AddTao()
{
cout << "装饰:手机外套" << endl;
}
virtual void show()
{
this->phone->show();
AddTao();
}
private:
Phone* phone;
};
int main(void)
{
Phone *phone = NULL;
DecoratorPhone* hasMoPhone = NULL;
DecoratorPhone* hasTaoPhone = NULL;
DecoratorPhone* hasMoTaoPhone = NULL;
// 定义一个 iphone12
phone = new Iphone("12");
// 给 iphone12 贴膜
hasMoPhone = new DecoratorPhoneMo(phone);
// 给 iphone12 加上皮套
hasTaoPhone = new DecoratorPhoneTao(phone);
hasMoPhone->show();
hasTaoPhone->show();
// 给有皮套的 iphone12 再贴膜
hasMoTaoPhone = new DecoratorPhoneMo(hasTaoPhone);
hasMoTaoPhone->show();
delete hasMoPhone;
delete hasTaoPhone;
delete hasMoTaoPhone;
delete phone;
return 0;
}
运行结果:
2.3 优缺点
(1) 对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加。
(2)可以通过一种动态的方式来扩展一个对象的功能,从而实现不同的行为。
(3) 可以对一个对象进行多次装饰。
(4)具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合“开闭原则”。
缺点:
(1) 使用装饰模式进行系统设计时 将产生很多小对象,大量小对象的产生势必会占用更多的系统资源,影响程序的性能。
2.4 适用场景
(1)动态、透明的方式给单个对象添加职责。
(2) 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展
和维护时可以使用装饰模式。
3 外观模式
外观模式即 Facade 模式,是由 GoF 提出的 23 种设计模式中的一种。Facade 模式为一组具有类似功能的类群,比如类库,子系统等等,提供一个一致的简单的界面。这个一致的简单的界面被称为 Facade 。
根据迪米特法则,如果两个类不直接通信,那么这两个类就不应该发生直接的相互作用。
外观模式就是将复杂的子类系统(SubSystem)抽象到同一个接口(Façade)进行管理,外界只需要通过此接口(Façade)与子类系统(SubSystem)进行交互,而不需要直接与复杂的子类系统(SubSystem)进行交互。
3.1 模式中的角色和职责
Façade(外观角色):为调用方 提供简单的调用接口。
SubSystem(子系统角色):功能提供者。指提供功能的类群(模块或子系
统)。
3.2 案例
示例:
根据类图,实现家庭影院外观模式应用。
实现KTV模式:电视打开,灯关掉,音响打开,麦克风打开,dvd打开。
实现游戏模式:电视打开,音响打开,游戏机打开。
示例代码:
#include <iostream>
using namespace std;
class TV
{
public:
void On()
{
cout << "电视打开了" << endl;
}
void Off()
{
cout << "电视关闭了" << endl;
}
};
class DVD
{
public:
void On()
{
cout << "DVD打开了" << endl;
}
void Off()
{
cout << "DVD关闭了" << endl;
}
};
class Xbox
{
public:
void On()
{
cout << "Xbox打开了" << endl;
}
void Off()
{
cout << "Xbox关闭了" << endl;
}
};
class MikePhone
{
public:
void On()
{
cout << "MikePhone打开了" << endl;
}
void Off()
{
cout << "MikePhone关闭了" << endl;
}
};
class Light
{
public:
void On()
{
cout << "Light打开了" << endl;
}
void Off()
{
cout << "Light关闭了" << endl;
}
};
class HomePlayer
{
public:
//ktv模式的接口
void doKTV()
{
light.Off();
tv.On();
dvd.On();
}
//游戏模式的接口
void doGame()
{
tv.On();
xbox.On();
}
Light light;
TV tv;
MikePhone mike;
Xbox xbox;
DVD dvd;
};
int main(void)
{
HomePlayer hp;
cout << "进入ktv模式" << endl;
hp.doKTV();
cout << "进入游戏模式" << endl;
hp.doGame();
return 0;
}
运行结果:
3.3 优缺点
优点:
(1)它对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并
使得子系统使用起来更加容易。通过引入外观模式,客户端代码将变得很简
**单,与之关联的对象也很少。 **
(2)它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会
**影响到调用它的客户端,只需要调整外观类即可。 **
(3)一个子系统的修改对其他子系统没有任何影响。
缺点:
(1)不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类
做太多的限制则减少了可变性和灵活性。
(2)如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了
开闭原则。
3.4 适用场景
(1)复杂系统需要简单入口使用。
(2)客户端程序与多个子系统之间存在很大的依赖性。
(3)在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与
层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度
4 适配器模式
适配器模式(Adapter Pattern):将一个类的接口转换成客户希望的另外一个接口。
适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
4.1 模式中的角色和职责
Target(目标抽象类):目标抽象类 定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。
Adaptee(适配者类):适配者即被适配的角色,它 定义了一个已经存在的接口,这个接口需要适配,适配者类 一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。
Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对 Adaptee 和 Target 进行适配,适配器类是适配器模式的核心,在对象适配器中,它 **通过继承 Target 并关联一个 Adaptee 对象使二者产生联系。 **
4.2 案例
示例代码:
#include <iostream>
using namespace std;
// Target(目标抽象类),5V 电压
class V5
{
public:
virtual void useV5() = 0;
};
// Adaptee(适配者类),220V 电压
class V220
{
public:
virtual void useV220()
{
cout << "用 220V 电压进行充电" << endl;
}
};
// Adapter(适配器类),手机充电器
class PhoneChargeAdapter : public V5
{
public:
virtual void useV5()
{
cout << "手机充电器对电压进行适配" << endl;
m_v220.useV220();
}
private:
V220 m_v220;
};
class Phone
{
public:
Phone()
{
v5 = new PhoneChargeAdapter;
}
~Phone()
{
if(v5 != NULL)
delete v5;
}
void charge()
{
cout << "手机进行充电" << endl;
v5->useV5();
}
private:
V5* v5;
};
int main(void)
{
Phone iphone;
iphone.charge();
return 0;
}
运行结果:
4.3 优缺点
优点:
(1)将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者
类,无须修改原有结构。
(2)增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类
中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者
类可以在多个不同的系统中复用。
(3)灵活性和扩展性都非常好,可以很方便地更换适配器,也可以在不修改
原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
缺点:
适配器中置换适配者类的某些方法比较麻烦
4.4 适用场景
(1)系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系
统的需要,甚至没有这些类的源代码。
(2)想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的
一些类,包括一些可能在将来引进的类一起工作
C++ 设计模式 3:结构型模式的更多相关文章
- Go语言实现的23种设计模式之结构型模式
摘要:本文主要聚焦在结构型模式(Structural Pattern)上,其主要思想是将多个对象组装成较大的结构,并同时保持结构的灵活和高效,从程序的结构上解决模块之间的耦合问题. 本文分享自华为云社 ...
- Java设计模式之结构型模式
结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元模式. 一.适配器模式: 意图: 将一个类的接口转换成客户希望的另外一个接口.Adapter 模式使得原本由于接 ...
- Java经典23种设计模式之结构型模式(一)
结构型模式包含7种:适配器模式.桥接模式.组合模式.装饰模式.外观模式.享元模式.代理模式. 本文主要介绍适配器模式和桥接模式. 一.适配器模式(Adapter) 适配器模式事实上非常easy.就像手 ...
- GoF的23种设计模式之结构型模式的特点和分类
结构型模式描述如何将类或对象按某种布局组成更大的结构.它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象. 由于组合关系或聚合关系比继承关系耦合度低,满足 ...
- GoF23种设计模式之结构型模式之桥接模式
一.概述 将类的抽象部分与实现分部分离开来,使它们都可以独立地变化. 二.适用性 1.你不希望在抽象和实现之间有一个固定的绑定关系的时候.例如:在程序运行时实现部分应可以被选择或切换. ...
- GoF23种设计模式之结构型模式之组合模式
一.概述 将对象组合成树型结构以表示“部分--整体”的层次关系.组合模式使得用户对单个对象和组合对象的使用具有一致性. 二.适用性 1.你想表示对象的部分--整体层次结构的时候. 2.你希望用户忽略组 ...
- GoF23种设计模式之结构型模式之外观模式
一.概述 为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用. 二.适用性 1.当你要为一个复杂子系统提供一个简单接口的时候.子系统 ...
- GoF23种设计模式之结构型模式之享元模式
一.概述 运用共享技术有效地支持大量细粒度的对象. 二.适用性 1.当一个应用程序使用了大量的对象的时候. 2.由于使用大量的独享而造成很大的存储开销的时候. 3.对象的大多数状态都可变为外部状态的 ...
- 设计模式(7)-结构型模式-Bridge模式
2.结构性模式 2.2 BRIDGE模式 别名:handle/body 这个模式体现了组合相对于继承的优势. 2.2.1动机 当一个抽象可能有多个实现时,通经常使用继承来协调它们.抽象类定义对该抽象 ...
- 设计模式-Proxy(结构型模式)
以下代码来源: 设计模式精解-GoF 23种设计模式解析附C++实现源码 //Proxy.h #pragma once class Subject { public: virtual ~Subject ...
随机推荐
- Centos7 Docker配置TLS认证的远程端口的证书生成教程(shell脚本一键生成)
通过 TLS来进行远程访问 百度百科 - TLS.我们需要在远程 docker 服务器(运行 docker 守护进程的服务器)生成 CA 证书,服务器证书,服务器密钥,然后自签名,再颁发给需要连接远程 ...
- 2018年10月份编程语言排行榜(来自TIOBE Index for October 2018)
TIOBE Index for October 2018 from:https://www.tiobe.com/tiobe-index// October Headline: Swift is kno ...
- 【转】Linux-CentOS7设置程序开启自启步骤!
链接:https://blog.csdn.net/wang123459/article/details/79063703
- 超好用的UnixLinux 命令技巧 大神为你详细解读
1.删除一个大文件 我在生产服务器上有一个很大的200GB的日志文件需要删除.我的rm和ls命令已经崩溃,我担心这是由于巨大的磁盘IO造成的,要删除这个大文件,输入: > /path/to/fi ...
- day40 Pyhton 并发编程03
一.内容回顾 进程是计算机中最小的资源分配单位 进程与进程之间数据隔离,执行过程异步 为什么会出现进程的概念? 为了合理利用cpu,提高用户体验 多个进程是可以同时利用多个cpu的,可以实现并行的效果 ...
- Git软件操作过程
一.下载 Git 二.下载Git小乌龟-TortoiseGit 三.汉化-去官网下载,官网地址 https://tortoisegit.org/download/
- python 不可变类型
不可变类型有:字符串,元祖,数字 可变类型:列表,字典 字典中,可变类型不能为key值 #在函数中 可变类型,为全局变量时,会变化 不可变类型,为全局变量时,不会变化
- 适合刚刚学习编程的萌新:C语言编程学习制作超简单又好玩的报数游戏!
C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构.C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现 ...
- 【最大匹配+二分答案】POJ 3057 Evacuation
题目大意 POJ链接 有一个\(X×Y\)的房间,X代表墙壁,D是门,.代表人.这个房间着火了,人要跑出去,但是每一个时间点只有一个人可以从门出去. 问最后一个人逃出去的最短时间,如果不能逃出去,输出 ...
- centos8上添加sudoer用户
一,检查服务器是否已安装sudo的rpm包? 1,查询rpm包列表 [root@yjweb ~]# rpm -qa | grep sudo libsss_sudo-2.0.0-43.el8_0.3.x ...