欢迎访问原文链接UML
意图
为其他对象提供一种代理以控制对这个对象的访问。
别名
Surrogate
动机
**对一个对象进行访问控制的原因是我们确实需要这个对象时才对它进行创建和初始化。**好多博客都用火车票代售点这个例子来解释代理模式,虽然这个例子贴近生活,容易理解。但是并不能很好地解释代理模式最重要的作用以及其优点。
这里使用书上的例子来讲解个人感觉更好。
考虑一个可以在文档中嵌入图形对象的文档编辑器。有些图形的创建开销很大。但是打开文档必须很迅速,因此我们在打开文档时应避免一次性创建很大的对象。因为并非所有图形对象在文档中都同时可见,所以没必要同时创建这些对象。
这一限制条件意味着,对于每一个开销很大的对象,应该根据需要进行创建,当一个图像变为可见时会产生这种需求,此时问题转变为:在文档中用什么来代替这些对图像?如何隐藏根据需要创建图像这一事实,即这种优化不应该影响到其他的代码。
问题的解决方案是用另一个对象来替代那个真正的图像,Proxy可以代替这个对象,并且在需要时负责实例化这个图像对象。
适用性
在需要用比较通用和复杂的对象指针(感觉这里指的就是代理)代替简单的指针的时候,可以使用Proxy模式。
-
远程代理(Remote Proxy):为一个对象在不同的地址空间提供局部代表,使得系统可以将服务端的实现隐藏,客户端不必考虑服务端的存在,例如Android中的Binder。这种代理有时被称为的大使(ambassador)
-
虚代理(Virtual Proxy):根据需要创建开销很大的对象时
-
保护代理(Protection Proxy):控制对原始对象的访问,保护代理用于对象应该有不同的访问权限时。
-
智能指引(Smart Reference):取代简单的指针,在访问对象时执行一些附加操作
- 对指向实际对象的引用计数,当没有该对象的引用时,可以自动释放它。
- 当第一次引用一个持久对象时,将它装入内存
- 在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它
结构
代理模式的结构如下图:
效果
Proxy 模式在访问对象时引入了一定程度的间接性,根据代理的类型,附加的间接性有多种用途:
- Remote Proxy:可以隐藏一个对象存在于不同地址空间的事实
- Virtual Proxy:可以进行最优化,例如更具要求创建对象
- Protection Proxies 和 Smart Reference 都允许在访问一个对象时有一些附加的内务处理(housekeeping task)
copy-on-write
另外 Proxy 还可以对用户隐藏另一种称为 copy-on-write 的优化方式,该优化于根据需要创建对象有关。
解释:拷贝一个庞大而复杂的对象是一种开销很大的操作,如果这个拷贝根本没有被修改,那么这些开销就没有必要。用代理延迟这一拷贝过程,我们可以保证只有当这个对象被修改时才对它进行拷贝。
在实现COW时必须对实体进行引用计数,拷贝代理仅会增加引用计数。只有当用户请求一个修改该实体的操作时,代理才会真正地拷贝它。在这个过程中,代理还必须减少实体的引用计数。当引用的数目为零时,这个实体将被删除。
相似模式
Adapter:适配器为它所适配的对象提供了一个不同的接口,相反,代理模式提供了与它的实体相同的接口。
Decorator:装饰的实现与代理类似,但是装饰的目的不一样,装饰为对象添加功能,而代理则是控制对对象的访问。
示例代码
这里演示虚拟代理的代码
public interface Image {
void display();
}
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk();
}
@Override
public void display() {
System.out.println("Display " + fileName);
}
public void loadFromDisk() {
// consume 100 seconds
System.out.println("Loading " + fileName);
}
}
public class ProxyImage implements Image{
private String fileName;
private RealImage realImage;
public ProxyImage(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(fileName);
}
realImage.display();
}
}
public class ProxyTest {
public static void main(String[] args) {
Image image = new ProxyImage("test_10mb.jpg");
//image will be loaded from disk
image.display();
System.out.println("");
//image will not be loaded from disk
image.display();
}
}