【设计模式】外观模式与代理模式

本文最后更新于:2023年12月17日 下午

Facade(外观)——对象结构模式

1. 意图

为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

2. 动机

将一个系统划分成若干个子系统有利于降低系统的复杂性。一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小。达到该目标的途经之一就是引入一个外观(Facade)对象,他为子系统中较为一般的设施提供了一个单一而简单的界面。

3. 适用性

以下情况使用Facade模式:

  • 当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂,大多数模式使用时都会产生更多更小的类。这使得子系统具有可复用性,也更容易对子系统进行定制,但也给那些不需要定制子系统的用户带来一些使用上的困难。Facade可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多可定制性的用户可以越过Facade层。
  • 客户程序与抽象类的实现部分之间存在着很大的依赖性。引入Facade将这个子系统与客户及其他的子系统分离,可以提高子系统的独立性和可移植性。
  • 当你需要构建一个层次结构的系统时,使用Facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,可以让它们仅通过Facade进行通信,从而简化了它们之间的依赖关系。

4. 结构

结构图

5. 参与者

  • Facade
    • 知道哪些子系统类负责处理请求
    • 将客户的请求代理给适当的子系统对象
  • Subsystem Class
    • 实现子系统的功能
    • 处理由Facade对象指派的任务
    • 没有Facade的任何相关信息,即没有指向Facade的指针

6. 协作

  • 客户程序通过发送请求给Facade的方式与子系统通信,Facade将这些消息转发给适当的子系统对象。尽管是子系统中的有关对象在做实际工作,但Facade模式本身也必须将它的接口转换成子系统的接口。
  • 使用Facade的客户程序不需要直接访问子系统对象。

7. 效果

Facade模式有下面的一些有点:

  • 它对客户屏蔽子系统组件,因而减少了客户处理的对象数目并使得子系统使用起来更加方便
  • 它实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件往往是紧耦合的。松耦合关系使得子系统的组件变化不会影响到它的客户。Facade模式有助于建立层次结构系统,也有助于对对象之间的依赖关系分层。Facade模式可以消除复杂的循环依赖关系。在大型软件系统中降低编译依赖性至关重要。
  • 如果应用需要,它并不限制他们使用子系统类,因此你可以在系统易用性和通用性之间加以选择。

8. 示例代码

测试模拟场景,Client 通过 Painter 这个 Facade,来绘制图形,根据需求改变大小和填充颜色,隐藏具体实现细节包括 图形Shape,颜色Color,线条边框Line。类图如下所示,为了简化实现,所以考虑各子系统之间的依赖关系。

类图

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
// Facade.h
// Created by 张宇轩 on 2022/4/30.
// Copyright (c) 2022 zhangyuxuan. All rights reserved.
//
#include <iostream>
#include <unordered_map>
using namespace std;
#ifndef STRUCTURALPATTERNDESIGN_FACADE_H
#define STRUCTURALPATTERNDESIGN_FACADE_H

/**
* @brief 模拟场景,Client 通过 Painter 这个 Facade,来绘制图形,根据需求改变大小和填充颜色
* 隐藏具体实现细节包括 图形Shape,颜色Color,线条边框Line
*/

namespace facade {
void test_facade();

class Shape{
public:
Shape(int size = 0) : size(size) {}
void set_size(int size) {
this->size = size;
}
virtual void draw_shape() {}

protected:
int size;
};
class Rectangle : public Shape{
public:
void draw_shape() {
cout << "绘制一个长方形,size为" << size << endl;
}
};
class Circle : public Shape{
public:
void draw_shape() {
cout << "绘制一个圆形,size为" << size << endl;
}
};

class Color {
public:
virtual void fill_color() {}
};
class Red : public Color {
void fill_color() {
cout << "填充红色" << endl;
}
};
class Blue : public Color {
void fill_color() {
cout << "填充蓝色" << endl;
}
};
class Line {
public:
virtual void draw_line() {}
};
class SolidLine : public Line {
public:
void draw_line() {
cout << "绘制实线边框" << endl;
}
};
class DottedLine : public Line {
public:
void draw_line() {
cout << "绘制虚线边框" << endl;
}
};

class Painter {
public:
enum ShapeType {
Rectangle,
Circle
};
enum ColorType{
Red,
Blue
};
enum LineType {
SolidLine,
DottedLine
};

Painter() {
shape_map[Rectangle] = new class Rectangle;
shape_map[Circle] = new class Circle;
color_map[Red] = new class Red;
color_map[Blue] = new class Blue;
line_map[SolidLine] = new class SolidLine;
line_map[DottedLine] = new class DottedLine;
}
~Painter() {
delete shape_map[Rectangle];
delete shape_map[Circle];
delete color_map[Red];
delete color_map[Blue];
delete line_map[SolidLine];
delete line_map[DottedLine];
}

void draw(ShapeType shape_type, int size, ColorType color_type, LineType line_type) {
shape_map[shape_type]->set_size(size);
shape_map[shape_type]->draw_shape();
color_map[color_type]->fill_color();
line_map[line_type]->draw_line();
}

private:
unordered_map<ShapeType, Shape*> shape_map;
unordered_map<ColorType, Color*> color_map;
unordered_map<LineType, Line*> line_map;
};

} // namespace facade

#endif //STRUCTURALPATTERNDESIGN_FACADE_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Facade.cpp
// Created by 张宇轩 on 2022/4/30.
// Copyright (c) 2022 zhangyuxuan. All rights reserved.
//
#include <iostream>

#include "Facade.h"

namespace facade {
void test_facade() {
cout << "——————————开始测试外观/门面模式——————————" << endl;

Painter p;
p.draw(Painter::Rectangle, 2, Painter::Red, Painter::SolidLine);
p.draw(Painter::Circle, 3, Painter::Blue, Painter::DottedLine);

cout << "——————————开始测试外观/门面模式——————————" << endl;

}
}

9. 相关模式

Abstract Factory 模式可以与Facade模式一起使用以提供一个接口,这一接口可用来以一种子系统独立的方式创建子系统对象。Abstract Factory也可以代替Facade模式隐藏那些与平台相关的类。

Mediator模式与Facade模式的相似之处是,它抽象了一些已有类的功能。然而,Mediator目的是对同事之间的任意通讯进行抽象,通常集中不属于任何单个对象的功能。Mediator的同事对象知道中介者并与它通信,而不是直接与其他同类对象通信。相对而言,Facade模式仅对子系统对象的接口进行抽象,从而使它们更容易使用;它并定义不功能,子系统也不知道facade的存在

一般来说,仅需要一个Facade对象,因此Facade对象通常属于Singleton对象。

Proxy(代理)——对象结构模型

1. 意图

为其他对象提供一种代理以控制对这个对象的访问。

2. 动机

对一个对象进行访问控制的一个原因是为了只有在我们确实需要这个对象时才对它进行创建和初始化。

我们考虑一个可以在文档中嵌入图形对象的文档编辑器。有些图形对象的创建开销很大。但是打开文档必须很迅速,因此我们打开文档时应该避免一次性创建所有开销很大的对象。因为并非所有这些对象在文档中都同时可见,所以也没有必要同时创建这些对象。

这一限制条件意味着,对于每一个开销很大的对象,应该根据需要进行创建,当一个图形变为可见时会产生这样的需要。但是在文档中我们用什么来替代这个图像呢?我们又如何才能隐藏根据需要创建图像这一事实,从而不会使得编辑器的实现复杂化呢?例如,这种优化不应该影响绘制和格式化的代码。

问题的解决方案是使用另一个对象,即图像Proxy,替代那个真正的图像。Proxy可以代替一个图形对象,并且在需要时负责实例化这个图像对象。

只有当文档编辑器激活图像代理的Draw操作以显示这个图像的时候,图像Proxy才创建真正的图像。Proxy直接将随后的请求转发给这个图像对象。因此在创建这个图像以后,它必须有一个指向这个图像的引用。

3. 适用性

在需要比较通用和复杂的对象指针代替简单的指针的时候,使用Proxy模式。下面是一些可以使用Proxy模式的常见情况:

  • 远程代理(Remote Proxy):为一个对象在不同的地址空间提供局部代表。
  • 虚代理(Virtual Proxy):根据需要创建开销很大的对象。在动机一节描述的ImageProxy就是这样一种代理的例子。
  • 保护代理(Protection Proxy):控制原始对象的访问。保护代理用于对象应该有不同的访问权限的时候。
  • 智能指针(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作,它的典型用途包括:
    • 对指向实际对象的引用技术,这样当对象没有引用时,可以自动释放它。
    • 当第一次引用一个持久对象时,将它装入内存。
    • 在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。

4. 结构

结构图

下图则是运行时一种可能的代理结构的对象图。

运行时对象图

5. 参与者

  • Proxy:
    • 保存一个引用使得代理可以访问实体。若RealSubject和Subject的接口相同,Proxy会引用Subject。
    • 提供一个与Subject的接口相同的接口,这样代理就可以用来替代实体
    • 控制对实体的存取,并可能负责创建和删除它。
    • —— 其他功能依赖于代理的类型:
      • Remote Proxy负责对请求及其参数进行编码,并向不同地址空间中的实体发送已编码的请求
      • Virtual Proxy可以缓存实体的附加信息,以便延迟对它的访问。
      • Protection Proxy检查调用者是否具有实现一个请求所必需的访问权限。
  • Subject
    • 定义RealSubject 和 Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy
  • RealSubject
    • 定义Proxy所代表的实体

6. 协作

代理根据其种类,在适当的时候向RealSubject转发请求。

7. 效果

Proxy模式在访问对象时引入一定程度的间接性。根据代理的类型,附加的间接性有多种用途:

  • Remote Proxy可以隐藏一个对象存在于不同地址空间的事实上。
  • Virtual Proxy 可以进行最优化,例如根据要求创建对象。
  • Protection Proxy 和 Smart Reference 都允许在访问对象时有一些附加的内务处理。

Proxy模式还可以对用户隐藏另一种被称为copy-on-write的优化方式, 该优化与根据需要创建对象有关。

8. 实现

对于C++ 来说,可以重载C++ 中的存取运算符。 C++支持重载运算符 -> 。重载这一运算法使你可以在撤销一个对象的引用时,执行一些附加的操作。这一点可以用于实现某些种类的代理,代理的作用就像是一个指针。

Proxy并不是总是需要知道实体的类型。若Proxy类能够完全通过一个抽象接口处理它的实体,则无须为每一个RealSubject类都生成一个Proxy类,Proxy类可以统一处理所有的RealSubject类。但是如果Proxy要实例化RealSubject(例如在虚代理中),那么他们必须要知道具体的类。

9. 示例代码

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
// Proxxy.h
// Created by 张宇轩 on 2022/4/30.
// Copyright (c) 2022 zhangyuxuan. All rights reserved.
//
#include <iostream>
#include <string>

using namespace std;

#ifndef STRUCTURALPATTERNDESIGN_PROXXY_H
#define STRUCTURALPATTERNDESIGN_PROXXY_H

/**
* @brief 模拟实现 Virtual Proxy
* 我们考虑一个可以在文档中嵌入图形对象的文档编辑器。有些图形对象的创建开销很大。
* 但是打开文档必须很迅速,因此我们打开文档时应该避免一次性创建所有开销很大的对象。
* 因为并非所有这些对象在文档中都同时可见,所以也没有必要同时创建这些对象。
* 只有当文档编辑器激活图像代理的Draw操作以显示这个图像的时候,图像Proxy才创建真正的图像。
* Proxy直接将随后的请求转发给这个图像对象。因此在创建这个图像以后,它必须有一个指向这个图像的引用。
*/

void test_proxy();

// 图形显示相关接口
class Graphic {
public:
virtual void draw() = 0;
};

class Image : public Graphic{
public:
Image (string file_name) {
// 从磁盘加载文件
cout << "从磁盘加载文件:" << file_name << endl;
data = "^_^";
}
void draw() {
cout << "绘制图片" << data << endl;
}
private:
// 图片的数据表示
string data;
};

class ImageProxy : public Graphic{
public:
ImageProxy(string file_name) {
this->file_name = file_name;
image = nullptr;
}
void draw() {
get_image()->draw();
}
protected:
Image* get_image() {
if (image == nullptr) {
image = new Image(file_name);
}
return image;
}
private:
Image* image;
string file_name;
};

class TextDocumnet {
public:
void insert(Graphic* graphic) {
this->graphic = graphic;
}
void show() {
// ...
// 当浏览到图片当前所在页
graphic->draw();
}

private:
Graphic* graphic;
};

#endif //STRUCTURALPATTERNDESIGN_PROXXY_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Proxxy.cpp
// Created by 张宇轩 on 2022/4/30.
// Copyright (c) 2022 zhangyuxuan. All rights reserved.
//
#include <iostream>

#include "Proxy.h"

void test_proxy() {
cout << "——————————开始测试代理模式——————————" << endl;

TextDocumnet doc;
doc.insert(new ImageProxy("an image"));
// doc 显示了一些别的东西
// doc 需要加载图片时
doc.show();

cout << "——————————结束测试代理模式——————————" << endl;

}

10. 相关模式

Adapter:适配器为它所适配的对象提供了一个不同的接口。相反,代理提供了与它的实体相同的接口。然而,用于访问保护的代理可能会拒绝执行实体会执行的操作。因此,它的接口实际上可能只是实体接口的一个子集。

Decorator:尽管装饰的实现部分与代理相似,但装饰的目的不一样。装饰为对象添加一个或多个功能,而代理则控制对对象的访问。

代理的实现和装饰的实现类似,但在相似的程度上有所差别。Protection Proxy的实现可能与装饰的实现差不多。另外,Remote Proxy不包含对实体的直接引用,而只是一个间接引用,如“主机ID,主机上的局部地址”。Virtual Proxy开始的时候使用一个间接引用,例如一个文件名,但最终将获取并使用一个直接引用。


【设计模式】外观模式与代理模式
https://2017zhangyuxuan.github.io/2022/04/30/2022-04/2022-04-30【设计模式】外观模式与代理模式/
作者
Zhang Yuxuan
发布于
2022年4月30日
许可协议