结构型模式

学习笔记之C++设计模式——创建型模式

📑 设计模式之创建型模式

📑 设计模式之行为型模式

Proxy模式又叫做代理模式,是构造型的设计模式之一, 它可以为其他对象提供-种代理( Proxy )以控制对这个对象的访问。所谓代理,是指具有与代理元(被代理的对象)具有相同的接口的类,客户端必须通过代理与被代理的目标类交互,而代理一-般在交互的过程中 (交互前后) , 进行某些特别的处理。

代理模式

代理模式

通俗理解就和海外代购类似,由一个海外代购专门负责所有需要从其他国家购买的这个行为。

以下为代理模式案例及其代码:代理模式案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// 代理模式.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;
class Item
{
public:
Item(string kind, bool fact)
{
this->kind = kind;
this->fact = fact;
}

string getKind()
{
return kind;
}

bool getFact()
{
return fact;
}
protected:
private:
string kind;
bool fact;
};

// 抽象的购物方式
class Shopping
{
public:
virtual void buy(Item *it) = 0;
protected:
private:
};

class KoeraShopping : public Shopping
{
public:
void buy(Item *it) {
cout << "去韩国买了" << it->getKind() << endl;
}
protected:
private:
};

class USAShopping : public Shopping
{
public:
void buy(Item * it) {
cout << "去美国买了" << it->getKind() << endl;
}
protected:
private:
};


// 海外代理
class OverseasProxy : public Shopping
{
public:
OverseasProxy(Shopping *shopping)
{
this->shopping = shopping;
}
virtual void buy(Item *it)
{
if (it->getFact() == true)
{
cout << "发现正品" << endl;
shopping->buy(it);
cout << "---------安检-----------" << endl;
}
else
{
cout << "发现假货" << it->getKind() << endl;
}
}
protected:
private:
Shopping *shopping; // 有一个购物方式
};


int main()
{
Item it1("nike鞋子", true);
Item it2("CET4证书", false);

Shopping *Koerashopping = new KoeraShopping;
Shopping *usaShopping = new USAShopping;

Shopping *overseaProxy = new OverseasProxy(usaShopping);

overseaProxy->buy(&it1);
overseaProxy->buy(&it2);

return 0;
}

代理模式的种类

远程代理(Remote Proxy)

为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可以是在另外一台主机中,远程代理又称为大使(Ambassador)。

远程代理: 使得客户端程序可以访问在远程主机上的对象,远程主机可能具有更好的计算性能与处理速度,可以快速相应并处理客户端的请求。远程代理可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。客户端完全可以认为被代理的远程业务对象是在本地而不是在远程,而远程代理对象承担了大部分的网络通信工作,并负责对远程业务方法的调用。

远程代理示意图

如图所示:

客户端对象不能直接访问远程主机中的业务对象,只能采取间接访问的方式。远程业务对象在本地主机中有一个代理对象,该代理对象负责对远程业务对象的访问和网络通信,它对于客户端对象而言是透明的。客户端无须关心实现具体业务的是谁,只需要按照服务接口所定义的方式直接与本地主机中的代理对象交互即可。

在基于.NET平台的分布式技术,例如DCOM(Distribute Component Object Mdoel, 分布式组件对象模型)、Web Service中,都应用了远程代理模式。

虚拟代理(Virtual Proxy)

如果需要创建一个资源消耗巨大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。虚拟代理(Virtual Proxy)也是一种常用的代理模式,对于一些占用系统资源较多或者加载时间较长的对象,可以给这些对象提供一个虚拟代理。在真实对象创建成功之前虚拟代理扮演真实对象的替身,而当真实对象创建之后,虚拟代理将用户的请求转发给真实对象。

通常,在以下两种情况下可以考虑使用虚拟代理:

(1) 由于对象本身的复杂性或者网络等原因导致一个对象需要较长的加载时间,此时可以用一个加载时间相对较短的代理对象来代表真实对象。通常在实现时可以结合多线程技术,一个线程用于显示代理对象,其他线程用于加载真实对象。这种虚拟代理模式可以应用在程序启动的时候,由于创建代理对象在时间和处理复杂度上要少于创建真实对象,因此,在程序启动时,可以用代理对象代替真实对象初始化,大大加速了系统的启动时间。当需要使用真实对象时,再通过代理对象来引用,而此时真实对象可能已经成功加载完毕,可以缩短用户的等待时间。

(2) 当一个对象的加载十分耗费系统资源的时候,也非常适合使用虚拟代理。虚拟代理可以让那些占用大量内存或处理起来非常复杂的对象推迟到使用它们的时候才创建,而在此之前用一个相对来说占用资源较少的代理对象来代表真实对象,再通过代理对象来引用真实对象。为了节省内存,在第一次引用真实对象时再创建对象,并且该对象可被多次重用,在以后每次访问时需要检测所需对象是否已经被创建,因此在访问该对象时需要进行存在性检测,这需要消耗一定的系统时间,但是可以节省内存空间,这是一种用时间换取空间的做法。

无论是以上哪种情况,虚拟代理都是用一个“虚假”的代理对象来代表真实对象,通过代理对象来间接引用真实对象,可以在一定程度上提高系统的性能。

保护代理(Protect Proxy)

控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限

缓冲代理(Cache Proxy)

为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果

缓冲代理(Cache Proxy)也是一种较为常用的代理模式,它为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,从而可以避免某些方法的重复执行,优化系统性能。

智能引用代理(Smart Reference Proxy)

当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。

装饰模式

装饰模式(decorator Pattern): 动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。

装饰模式

案例

装饰模式案例

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// 装饰模式.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;

// 一个抽象的构件,他是具体构件和抽象装饰类的父类
class Phone
{
public:
virtual void Show() = 0;
protected:
private:
};

// 具体的构件
class iPhone : public Phone
{
public:
iPhone(string kind) {
this->kind = kind;
}

virtual void Show() {
cout << "秀出了 iPhone- " << kind << ":" << endl;
}
protected:
private:
string kind;
};

// 具体的构件
class Mi : public Phone
{
public:
Mi(string kind) {
this->kind = kind;
}
virtual void Show() {
cout << "秀出了 Mi-" << kind << ":" << endl;
}
protected:
private:
string kind;
};

// 抽象的手机装饰类,必须包含抽象的构件
class DeCoratorPhone : public Phone
{
public:
DeCoratorPhone() {

}
DeCoratorPhone(Phone *phone) {
this->phone = phone;
}
virtual void Show() {
this->phone->Show();
}
protected:
private:
Phone *phone;
};

// 具体的装饰器
class DeCoratorPhoneMo : public DeCoratorPhone {

public:
DeCoratorPhoneMo(Phone *phone) {
this->phone = phone;
}
virtual void Show() {
this->phone->Show();
addMo();
}
void addMo() {
cout << "装饰: 手机贴膜" << endl;
}

private:
Phone *phone;

};

// 具体的装饰器
class DeCoratorPhoneKe : public DeCoratorPhone {

public:
DeCoratorPhoneKe(Phone *phone) {
this->phone = phone;
}
virtual void Show() {
this->phone->Show();
addMo();
}
void addMo() {
cout << "装饰: 手机装保护壳" << endl;
}

private:
Phone *phone;

};

int main()
{
Phone *phone = NULL;
DeCoratorPhone * hasMoPhone = NULL;
DeCoratorPhone * hasKePhone = NULL;
DeCoratorPhone * hasMoKePhone = NULL;

// 定义一个Iphone 13手机
phone = new iPhone("iPhone 13");
// 给普通iphone 加上贴膜
hasMoPhone = new DeCoratorPhoneMo(phone);
// 给普通iphone 加上保护套
hasKePhone = new DeCoratorPhoneKe(phone);
hasMoPhone->Show();
hasKePhone->Show();

// 给有皮套的iphone 加上贴膜
hasMoKePhone = new DeCoratorPhoneMo(hasKePhone);
hasMoKePhone->Show();

delete hasMoKePhone;
delete hasKePhone;
delete hasMoPhone;
delete phone;
cout << "---------------------------------------" << endl;

phone = new Mi("Mix 4");
hasMoPhone = new DeCoratorPhoneMo(phone);
hasKePhone = new DeCoratorPhoneKe(phone);
hasMoPhone->Show();
hasKePhone->Show();

hasMoKePhone = new DeCoratorPhoneKe(hasKePhone);
hasMoKePhone->Show();

delete hasMoKePhone;
delete hasKePhone;
delete hasMoPhone;
delete phone;

return 0;
}

输出:

秀出了 iPhone- iPhone 13:
装饰: 手机贴膜
秀出了 iPhone- iPhone 13:
装饰: 手机装保护壳
秀出了 iPhone- iPhone 13:
装饰: 手机装保护壳
装饰: 手机贴膜
秀出了 Mi-Mix 4:
装饰: 手机贴膜
秀出了 Mi-Mix 4:
装饰: 手机装保护壳
秀出了 Mi-Mix 4:
装饰: 手机装保护壳
装饰: 手机装保护壳
请按任意键继续. . .
## 装饰模式的优缺点

优点

  1. 对于扩展一个对象的功能,装饰模式比继承更加灵活,不会导致类的个数急剧增加。
  2. 可以通过一种动态的方式来扩展一个对象的功能,从而实现不同的行为。
  3. 可以对一个对象进行多次装饰。
  4. 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无需改变,符合“开闭原则”。

缺点

  1. 使用装饰模式进行系统设计时产生很多小对象,大量小对象的产生势必会占用更多的系统资源,影响程序的性能。

适用场景

  1. 动态、透明的方式给耽搁对象添加职责
  2. 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式。

装饰器模式关注于在一个对象上动态的添加方法。然而代理模式关注于控制对象的访问。换句话说,用代理模式,代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例。并且,当我们使用装饰器模式的时候,我们通常的做法是将院士对象作为一个参数创给装饰者的构造器。


外观模式

根据迪米特法则,如果两个类不必彼此直接通信,那么两个类就不应当发生直接的相互作用。

一个对象应当对其他对象尽可能少的了解,从而降低各个对象之间的耦合,提高系统的可维护性。

例如在一个程序中,各个模块之间相互调用时,通常会提供一个统一的接口来实现。这样其他模块不需要了解另外一个木块的内部实现细节,当一个模块内部的实现发生改变是,不会影响其他模块的使用。(黑盒原理)。

Facade模式也叫外观模式。是有GoF提出的23中设计模式中的一种。Facade模式为一组具有类似功能的类群,比如类库、子系统等等,提供一个一致的简单的界面。这个一致的简单的界面被称作facade。

外观模式

Facade(外观角色): 为调用方,定义简单的调用接口

SubSystem(子系统角色): 功能提供者。指提供功能的类群(模块或子系统)。

更加具体的理解直接看代码就能明白。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// 外观模式.cpp : 定义控制台应用程序的入口点。
//
/*
外观模式就是将复杂的子类系统抽象到同一个的接口进行管理
外界只需要通过此接口与子类系统进行交互,而不必直接与复杂的子类系统进行交互。
*/
#include "stdafx.h"
#include <iostream>
using namespace std;

// 这里定义四个字了系统
class SubSystem1
{
public:
void Methor1()
{
cout << "子系统方法一" << endl;
}
protected:
private:
};

class SubSystem2
{
public:
void Methor2()
{
cout << "子系统方法二" << endl;
}
protected:
private:
};

class SubSystem3
{
public:
void Methor3()
{
cout << "子系统方法三" << endl;
}
protected:
private:
};

class SubSystem4
{
public:
void Methor4()
{
cout << "子系统方法四" << endl;
}
protected:
private:
};


/* 外观类,接口 */
class Facade
{
public:
Facade()
{
one = new SubSystem1;
two = new SubSystem2;
three = new SubSystem3;
four = new SubSystem4;
}
~Facade()
{
delete one;
delete two;
delete three;
delete four;
}

void MethorA()
{
cout << "方法组A()" << endl;
one->Methor1();
two->Methor2();
}

void MethorB()
{
cout << "方法组B()" << endl;
three->Methor3();
four->Methor4();
}


protected:
private:
SubSystem1 *one;
SubSystem2 *two;
SubSystem3 *three;
SubSystem4 *four;
};

int main()
{
Facade facade;
facade.MethorA();
facade.MethorB();
return 0;
}


输出:

方法组A()
子系统方法一
子系统方法二
方法组B()
子系统方法三
子系统方法四
请按任意键继续. . .

案例

外观模式——案例影院模式

外观模式较为简单,实现上述模式依次实现电视、灯、音响、麦克风、DVD、游戏机即可,在此基础上实现外观类Facade(家庭影院)即可。

外观模式的优缺点

优点

  1. 他对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易。通过引入外观模式,客户端代码将变得很简单,预支关联的对象也很少。
  2. 它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可。
  3. 一个子系统的修改对其他子系统没有任何影响。

缺点

  1. 不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性。
  2. 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则。

适用场景

  1. 复杂系统需要简单入口使用。
  2. 客户端程序与多个子系统之间存在很大的依赖性。
  3. 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。

适配器模式

将一个类的接口转换成客户希望的另外一个接口。使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

适配器模式中的角色和职责

Adapter

  1. Target(目标抽象类):目标抽象类定义客户所需接口,可以使一个抽象类或接口,也可以是具体类。
  2. Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,他通过继承Target并关联一个Adaptee对象使二者产生联系。
  3. Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。

根据对象适配器模式结构图,在对象适配器中,客户端需要调用request()方法,而适配者类Adaptee没有该方法,但是它所提供的SpecificRequest()方法确实客户端所需要的。为了是客户端能够使用适配者类,需要提供一个包装类Adapter,即适配器类。这个包装类包装了一个适配者的实例,从而将客户端与适配者衔接起来,在适配器的Request()方法中调用适配者的SpecificRequest()方法。因为适配器类与适配者类是关联关系(也可称之为委派关系),所以这种适配器模式称为对象适配器模式。

案例

适配器案例

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 适配器模式.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;

// target
class V5
{
public:
virtual void uesV5() = 0;
private:

};

class V220 {
public:
virtual void useV220() {
cout << "用220V电压进行充电" << endl;
}
};

// Adapter 充电器
class ChargerAdapter : public V5
{
public:
virtual void uesV5()
{
cout << "充电器对电压进行适配" << endl;
m_p220V.useV220();
}
protected:
private:
V220 m_p220V;
};

class Phone
{
public:
Phone()
{
v5Adapter = new ChargerAdapter;
}
~Phone()
{
if (v5Adapter != NULL)
{
delete v5Adapter;
v5Adapter = NULL;
}
}

void charge() {
cout << "手机进行充电" << endl;
v5Adapter->uesV5();
}

private:
// 5v 手机充电器
V5 *v5Adapter;
};

int main()
{
Phone iphone;
iphone.charge();
return 0;
}

输出:

手机进行充电
充电器对电压进行适配
用220V电压进行充电
请按任意键继续. . .

适配器模式的优缺点

优点

  1. 将目标类和适配者类解耦。通过引入一个适配器类来重用现有的适配者类,无需修改原有结构。
  2. 增加了类的透明性和复用性。将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
  3. 灵活性和扩展性都非常好。可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。

缺点

  1. 适配器中置换适配者类的某些方法比较麻烦。

适应场景

  1. 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
  2. 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

到此,设计模式之结构型模式。over~~~😀🌝

img