Fork me on GitHub

代理模式(Proxy Pattern)

概述

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

代理模式就是为其他对象提供一种代理来控制对这个对象的访问。

种类和用途

Proxy模式根据种类不同,效果也不尽相同:

1、远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代表对象。这个不同的地址空间可以是在本机器中,也可是在另一台机器中。远程代理又叫做大使(Ambassador)。好处是系统可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。客户完全可以认为被代理的对象是局域的而不是远程的,而代理对象承担了大部份的网络通讯工作。由于客户可能没有意识到会启动一个耗费时间的远程调用,因此客户没有必要的思想准备。

2、虚拟(Virtual)代理:根据需要创建一个资源消耗较大的对象,使得此对象只在需要时才会被真正创建。使用虚拟代理模式的好处就是代理对象可以在必要的时候才将被代理的对象加载;代理可以对加载的过程加以必要的优化。当一个模块的加载十分耗费资源的情况下,虚拟代理的好处就非常明显。

3、Copy-on-Write代理:虚拟代理的一种。把复制(克隆)拖延到只有在客户端需要时,才真正采取行动。

4、保护(Protector Access)代理:控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。保护代理的好处是它可以在运行时间对用户的有关权限进行检查,然后在核实后决定将调用传递给被代理的对象。

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

6、防火墙(Firewall)代理:保护目标,不让恶意用户接近。

7、同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。

8、智能引用(SmartReference)代理:当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。

在所有种类的代理模式中,虚拟(Virtual)代理、远程(Remote)代理、智能引用代理(SmartReference Proxy)和保护(Protector Access)代理是最为常见的代理模式。

类图和实例

Proxy-Pattern

代理模式所涉及的角色有:

抽象主题角色(Subject):声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题。

代理主题(Proxy)角色:代理主题角色内部含有对真是主题的引用,从而可以在任何时候操作真实主题对象;代理主题角色提供一个与真实主题角色相同的接口,以便可以在任何时候都可以替代真实主体;控制真实主题的应用,负责在需要的时候创建真实主题对象(和删除真实主题对象);代理角色通常在将客户端调用传递给真实的主题之前或之后,都要执行某个操作,而不是单纯的将调用传递给真实主题对象。

真实主题角色(RealSubject)角色:定义了代理角色所代表的真实对象。

这里给出一个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
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
// TestProxy.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <assert.h>

#define KSAFE_DELETE(p) \
if (p) \
{ \
delete p; \
p = NULL; \
}

class KRefCount
{
public:
KRefCount():m_nCount(0){}

public:
void AddRef(){m_nCount++;}
int Release(){return --m_nCount;}
void Reset(){m_nCount=0;}

private:
int m_nCount;
};

template <typename T>
class KSmartPtr
{
public:
KSmartPtr(void)
: m_pData(NULL)
{
m_pReference = new KRefCount();
m_pReference->AddRef();
}
KSmartPtr(T* pValue)
: m_pData(pValue)
{
m_pReference = new KRefCount();
m_pReference->AddRef();
}
KSmartPtr(const KSmartPtr<T>& sp)
: m_pData(sp.m_pData)
, m_pReference(sp.m_pReference)
{
m_pReference->AddRef();
}
~KSmartPtr(void)
{
if (m_pReference && m_pReference->Release() == 0)
{
KSAFE_DELETE(m_pData);
KSAFE_DELETE(m_pReference);
}
}

inline T& operator*()
{
return *m_pData;
}
inline T* operator->()
{
return m_pData;
}
KSmartPtr<T>& operator=(const KSmartPtr<T>& sp)
{
if (this != &sp)
{
if (m_pReference && m_pReference->Release() == 0)
{
KSAFE_DELETE(m_pData);
KSAFE_DELETE(m_pReference);
}

m_pData = sp.m_pData;
m_pReference = sp.m_pReference;
m_pReference->AddRef();
}

return *this;
}
KSmartPtr<T>& operator=(T* pValue)
{
if (m_pReference && m_pReference->Release() == 0)
{
KSAFE_DELETE(m_pData);
KSAFE_DELETE(m_pReference);
}
m_pData = pValue;
m_pReference = new KRefCount;
m_pReference->AddRef();

return *this;
}

T* Get()
{
T* ptr = NULL;
ptr = m_pData;

return ptr;
}
void Attach(T* pObject)
{
if (m_pReference->Release() == 0)
{
KSAFE_DELETE(m_pData);
KSAFE_DELETE(m_pReference);
}

m_pData = pObject;
m_pReference = new KRefCount;
m_pReference->AddRef();
}

T* Detach()
{
T* ptr = NULL;

if (m_pData)
{
ptr = m_pData;
m_pData = NULL;
m_pReference->Reset();
}
return ptr;
}

private:
KRefCount* m_pReference;
T* m_pData;
};

与其他模式的区别

1)适配器模式Adapter

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

2) 装饰器模式Decorator

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

总结

在软件系统中,加一个中间层是我们常用的解决方法,这方面Proxy模式给了我们很好的实现。