设计模式——代理模式
Provide a surrogate or placeholder for annother obeject to control access to it.
代理模式的结构有益于几乎所有的构建类设计模式。
它的 UML 图类似一个三角形
class Grapic
{
public:
virtual void draw() = 0;
virtual void save() = 0;
virtual void load() = 0;
};
class Image: public Grapic
{
public:
void draw() {
std::cout << "draw a image self" << std::endl;
}
void save() {
std::cout << "save on disk" << std::endl;
}
void load() {
std::cout << "load from disk .." << std::endl;
}
};
class ImageProxy: public Grapic
{
public:
ImageProxy(const char *imageFile, Context* context) {
_imageFile = imageFile;
_image = nullptr;
}
void draw() {
_image->draw();
}
void load() {
if (_context->getUser() == UserRole::Super)
return _image->load();
std::cout << "have not access right" << std::endl;
return;
}
protected:
Image* loadImage() {
if (_image == nullptr) {
return new Image();
}
return _image;
}
private:
char * imageFile;
Image *_image;
};
代理模式有两个典型的特征:
- 两个子类继承自一个父类,有时候可以是一个接口,这意味着两个子类具有一样的接口(某些virtual函数)
- 代理类持有一个被代理的引用——这种方法在行为类模式中颇为常见。
因为有第二条,所有代理总是可以通过这个引用操纵真实的对象。
乍看十分别扭的结构,它会在什么场景下有用呢?
因为 ImageProxy 几乎就是 Image的影子,但是它们只是在抽象接口上是一致的,彼此的 constructor 和 属性其实可以大不一样,Proxy可以控制对 被代理对象的访问——做到这一点,只需要在代码中,凡出现要传递 被代理对象的上层client代码中,代之以 Proxy 。
比方说,上面的 Image 的 load 动作,它是一个抽象的接口,
当从代理的 load 入口走进去的时候,先从上下文中获取一些类似角色的参数,校验其访问权限是否满足
这是一种类似权限控制的访问场景。因为 new Image() 的动作完全被代理负责。
- 第一种情况,对client 隐藏真实的对象,在client 上下文中,类似下面这种的写法
有一个client context 的上下文长这样
void operate(Graphic* g);
那么可以可以用代理调用
operate(new ImageProxy()) ; // C++ 注意持有 代理的指针进行内存管理
-
另一种情况是占位作用,一般如果类似 load() 这种的动作,是一个耗时操作,它返回一个 Image——上面的代码没有体现这点,但我们可以假设它其实是一个有 UI呈现的动作,当 load 发生时,用代理去做,而不是直接在图片中实现,这样代理可以充当一个占位符,先假设它完成了,然后上下文再在合适的时机,调用 draw 之类真实的绘制动作去完成。
这会引来一中效果,类似网页加载,那些比较清楚的图片,总是比较慢,我们先看到大多数文字,然后一张图片的占位符,类似下面这样。然后过一段时间,这张图真实地加载出来,——我们此时可能已经读完一百多字了。
占位图
- 一个真实的应用是 智能指针
智能指针 是 C++为了实现内存自动管理的一种技术——将原始的指针封装在一个有构造函数和析构函数的类中,利用栈对象自动调用析构的特性实现自动化释放内存。
在std::auto_ptr 里可能不太能清晰地看到代理模式的三角结构。但是如果我们把运算符 -> * 看成是一个公共接口的话,auto_ptr 和 裸指针对象都支持该运算,因而可以视为一种公共的接口,这样看来,它们完全符合代理模式的三角接口。
试用伪代码表达如下, -> 用接口 reference 表示,deReference代替 * 运算
于是有一个“看不见”的公共接口,大概定义成这样
template<class T>
class PointerInterface
{
public:
T* reference() = 0;
T deReference() = 0;
};
Pointer 具有 reference 和deReference操作。
于是智能指针 SmartPointer 也从PointerInterface 继承,然后持有Pointer 的句柄(引用) 这样,代理的三角结构是不是就很清晰了。
实际的实现中。
很关键的点也正是如此:
- 智能指针,不管是旧的 auto_ptr 还是新版C++11的 share_ptr unique_ptr 等等都一致的重载了运算符 -> 和 符号,这里不是乘法是指针的解引用运算。
- 其次持有裸指针的真身,从而实现对它指向的内存的“管理”