20220301 代理模式
明昧 Lv7

2022/03/01 Proxy代理模式

  • 结构型模式啥叫结构型,看不懂

简介

代理模式为其他对象提供一种代理以控制对这个对象的访问。 在需要用比较通用和复杂的对象指针代替简单的的指针的时候,使用代理模式。有四种常用的情况:

1、远程代理,也就是为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实。

2、虚拟代理,是根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象。

3、安全代理,用来控制真实对象访问的权限。

4、智能指针,取代了简单的指针,它在访问对象时执行一些附加操作。(????)

img

参与者:

  • Proxy

— 保存一个引用使得代理可以访问实体。若RealSubject和Subject的接口相同,proxy会引用Subject。

— 提供一个与Subject的接口相同的接口,这样代理就可以用来代替实体。

— 控制对实体的存取,并可能负责创建和删除它。

— 其它功能依赖于代理的类型。

  • Subject

— 定义RealSubject和Proxy的共用接口,这样就可以在任何使用RealSubject的地方都可以使用Proxy

  • RealSubject

— 定义Proxy所代表的实体。

思想:作为C工程师,免不了要管理内存,内存管理也是C中的难点,而智能指针采用引用计数的办法很方便的帮我们管理了内存的使用,极大方便了我们的工作效率。而智能指针的这种用法其实就是代理模式的一种,他帮我们控制了该对象的内存使用。代理模式就是为其他对象提供一种代理来控制对这个对象的访问。

场景:需要注意体会他和Decorator的需别。

Proxy是继承需要修饰的类,而Decorator用的是包含的方式。Proxy模式,或者准确地说DynamicProxy模式,是现代AOP框架实现中的一种常用方式。典型的实现如Spring,JBoss以及CastleProject中的Aspect。

实现:继承,并在过载方法中添加需要的修饰功能。

java实现代码

1
2
3
4
//Image.java
public interface Image {
void display();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//RealImage.java
public class RealImage implements Image {

private String fileName;

public RealImage(String fileName){
this.fileName = fileName;
loadFromDisk(fileName);
}

@Override
public void display() {
System.out.println("Displaying " + fileName);
}

private void loadFromDisk(String fileName){
System.out.println("Loading " + fileName);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//ProxyImage.java
public class ProxyImage implements Image{

private RealImage realImage;
private String fileName;

public ProxyImage(String fileName){
this.fileName = fileName;
}

@Override
public void display() {
if(realImage == null){
realImage = new RealImage(fileName);//这里就是实现加载的过程,同时也是绑定引用的过程
}
realImage.display();//如果已经加载过,直接相当于调用RealImage对象
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//真正调用
//ProxyPatternDemo.java
public class ProxyPatternDemo {

public static void main(String[] args) {
Image image = new ProxyImage("test_10mb.jpg");

// 图像将从磁盘加载
image.display();
System.out.println("");
// 图像不需要从磁盘加载
image.display();
}
}

C++实现代码

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
#include <iostream>

class Subject {
public:
virtual void Request() const = 0;
};

class RealSubject : public Subject {
public:
void Request() const override {
std::cout << "RealSubject: Handling request.\n";
}
};

class Proxy : public Subject {

private:
RealSubject* real_subject_;//实质上是一个引用

bool CheckAccess() const {

std::cout << "Proxy: Checking access prior to firing a real request.\n";
return true;
}
void LogAccess() const {
std::cout << "Proxy: Logging the time of request.\n";
}

public:
//这一句的前面和后面的* real_subject意思不一样!易混
Proxy(RealSubject* real_subject) : real_subject_(new RealSubject(*real_subject)) {
/*在这里的运行理解我暂且认为是这样的:new分为两个部分,申请内存空间,调用构造函数
编译器为RealSubject类自动生成了相关的构造函数
最后proxy指向的proxy对象中的real_subject_指向的RealSubject对象
和一开始real_subject = new RealSubject中的对象是同一个对象*/
}

~Proxy() {
delete real_subject_;
}

void Request() const override {
if (this->CheckAccess()) {
this->real_subject_->Request();
this->LogAccess();
}
}
};

void ClientCode(const Subject& subject) {
// ...
subject.Request();
// ...
}

int main() {
std::cout << "Client: Executing the client code with a real subject:\n";
RealSubject* real_subject = new RealSubject;
ClientCode(*real_subject);
std::cout << "\n";
std::cout << "Client: Executing the same client code with a proxy:\n";
Proxy* proxy = new Proxy(real_subject);
ClientCode(*proxy);

delete real_subject;
delete proxy;
return 0;
}

代码分析与总结

  • 代理对象里有内置引用,能够通过proxy对象加载成功后,像直接用realsubject一样相关操作调用RealSubject对象
  • 代理对象能够实现对真正实现对象的延后初始化,用的时候再进行加载,并且能够通过代理决定什么时候把这个对象缓存释放

实际应用场景

  • 延迟初始化(虚拟代理)。如果你有一个偶尔使用的重量级服务对象,一直保持该对象运行会消耗系统资源时,可使用代理模式。

    你无需在程序启动时就创建该对象, 可将对象的初始化延迟到真正有需要的时候。

    (难道我真正需要的时候再创建初始化对象不行吗?为什么还要裹一层皮)

  • 访问控制 (保护代理)。 如果你只希望特定客户端使用服务对象,这里的对象可以是操作系统中非常重要的部分,而客户端则是各种已启动的程序(包括恶意程序),此时可使用代理模式。

    代理可仅在客户端凭据满足要求时将请求传递给服务对象。

  • 本地执行远程服务 (远程代理)。 适用于服务对象位于远程服务器上的情形。

在这种情形中, 代理通过网络传递客户端请求, 负责处理所有与网络相关的复杂细节。

  • 记录日志请求 (日志记录代理)。 **适用于当你需要保存对于服务对象的请求历史记录时。 **代理可以在向服务传递请求前进行记录。

缓存请求结果 (缓存代理)。 适用于需要缓存客户请求结果并对缓存生命周期进行管理时, 特别是当返回结果的体积非常大时。

  • 智能引用。可在没有客户端使用某个重量级对象时立即销毁该对象。

应用实例和伪代码

1、Windows 里面的快捷方式。

2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。

3、买火车票不一定在火车站买,也可以去代售点。

4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。

5、spring aop。

  • 怎么说 还是有点看不懂人家写的伪代码,不知道配置是怎么实现的
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
// 远程服务接口。
interface ThirdPartyTVLib is
method listVideos()
method getVideoInfo(id)
method downloadVideo(id)

// 服务连接器的具体实现。该类的方法可以向腾讯视频请求信息。请求速度取决于
// 用户和腾讯视频的互联网连接情况。如果同时发送大量请求,即使所请求的信息
// 一模一样,程序的速度依然会减慢。
class ThirdPartyTVClass implements ThirdPartyTVLib is
method listVideos() is
// 向腾讯视频发送一个 API 请求。

method getVideoInfo(id) is
// 获取某个视频的元数据。

method downloadVideo(id) is
// 从腾讯视频下载一个视频文件。

// 为了节省网络带宽,我们可以将请求结果缓存下来并保存一段时间。但你可能无
// 法直接将这些代码放入服务类中。比如该类可能是第三方程序库的一部分或其签
// 名是`final(最终)`。因此我们会在一个实现了服务类接口的新代理类中放入
// 缓存代码。当代理类接收到真实请求后,才会将其委派给服务对象。
class CachedTVClass implements ThirdPartyTVLib is
private field service: ThirdPartyTVLib
private field listCache, videoCache
field needReset

constructor CachedTVClass(service: ThirdPartyTVLib) is
this.service = service

method listVideos() is
if (listCache == null || needReset)
listCache = service.listVideos()
return listCache

method getVideoInfo(id) is
if (videoCache == null || needReset)
videoCache = service.getVideoInfo(id)
return videoCache

method downloadVideo(id) is
if (!downloadExists(id) || needReset)
service.downloadVideo(id)

// 之前直接与服务对象交互的 GUI 类不需要改变,前提是它仅通过接口与服务对
// 象交互。我们可以安全地传递一个代理对象来代替真实服务对象,因为它们都实
// 现了相同的接口。
class TVManager is
protected field service: ThirdPartyTVLib

constructor TVManager(service: ThirdPartyTVLib) is
this.service = service

method renderVideoPage(id) is
info = service.getVideoInfo(id)
// 渲染视频页面。

method renderListPanel() is
list = service.listVideos()
// 渲染视频缩略图列表。

method reactOnUserInput() is
renderVideoPage()
renderListPanel()

// 程序可在运行时对代理进行配置。
class Application is
method init() is
aTVService = new ThirdPartyTVClass()
aTVProxy = new CachedTVClass(aTVService)
manager = new TVManager(aTVProxy)
manager.reactOnUserInput()

优缺点

1646463890159

与其他模式的关系

  • 装饰代理有着相似的结构, 但是其意图却非常不同。 这两个模式的构建都基于组合原则, 也就是说一个对象应该将部分工作委派给另一个对象。 两者之间的不同之处在于代理通常自行管理其服务对象的生命周期, 而装饰的生成则总是由客户端进行控制。
 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View