1. Proxy in the Real World
说到代理,首先想到的是买火车票。比如现在你想买一张票,有好多种方法,简单说三种: 古时候,我们都要去火车站售票厅买,里面的售票员会根据我们的需要,从内部系统中获取一张票卖给你。没有手续费。这里售票窗口可以看成是内部系统的代理。 后来,有了代售点,我们还是把购票需求告诉代售点售票员,然后代售点售票员连接火车站内部系统,获取票给你,但是要收取5块钱手续费。这里代售点可以看成是火车站售票窗口的代理,多了一些操作,中间收取了手续费。 在后来,互联网发达了,有了网上售票代理商,我们填写买票信息,就能拿到票。这里网上代理商也可以看成火车站内部系统的代理。
这些代理有一个共同的特点,那就是都可以售票,就好像他们与车票内部系统都实现了相同的接口。普通人是没有权限直接访问内部系统的,所以,必须经过代理渠道。 而这些代理除了提供访问内部系统的权限外,还会增加一些额外的控制操作。 比如,如果你没身份证,就不允许买票,这是一种权限控制; 比如,代售点需要收取5块手续费; 等。
2.Proxy in GoF
Allows for object level access control by acting as a pass through entity or a placeholder object.
使用代理模式创建代理对象,让代理对象控制目标对象的访问(目标对象可以是远程的对象、创建开销大的对象或需要安全控制的对象),并且可以在不改变目标对象的情况下添加一些额外的功能。
Subject
:抽象主题角色,抽象主题类可以是抽象类,也可以是接口,是一个最普通的业务类型定义,无特殊要求。RealSubject
:具体主题角色,也叫被委托角色、被代理角色。是业务逻辑的具体执行者。Proxy
:代理主题角色,也叫委托类、代理类。它把所有抽象主题类定义的方法给具体主题角色实现,并且在具体主题角色处理完毕前后做预处理和善后工作。(最简单的比如打印日志)
3.代理模式的应用形式
- 远程代理(Remote Proxy) -可以隐藏一个对象存在于不同地址空间的事实。也使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
- 虚拟代理(Virtual Proxy) – 允许内存开销较大的对象在需要的时候创建。只有我们真正需要这个对象的时候才创建。
- 写入时复制代理(Copy-On-Write Proxy) – 用来控制对象的复制,方法是延迟对象的复制,直到客户真的需要为止。是虚拟代理的一个变体。
- 保护代理(Protection (Access) Proxy) – 为不同的客户提供不同级别的目标对象访问权限
- 缓存代理(Cache Proxy) – 为开销大的运算结果提供暂时存储,它允许多个客户共享结果,以减少计算或网络延迟。
- 防火墙代理(Firewall Proxy) – 控制网络资源的访问,保护主题免于恶意客户的侵害。
- 同步代理(Synchronization Proxy) – 在多线程的情况下为主题提供安全的访问。
- 智能引用代理(Smart ReferenceProxy) - 当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。
- 复杂隐藏代理(Complexity HidingProxy) – 用来隐藏一个类的复杂集合的复杂度,并进行访问控制。有时候也称为外观代理(Façade Proxy),这不难理解。复杂隐藏代理和外观模式是不一样的,因为代理控制访问,而外观模式是不一样的,因为代理控制访问,而外观模式只提供另一组接口。
4. 什么时候用
Proxy Pattern Tutorial with Java Examples
- The object being represented is external to the system.
- Objects need to be created on demand.
- Access control for the original object is required
- Added functionality is required when an object is accessed.
通常,在项目中我们会把代价高的第三方调用(比如远程调用)包装到代理中。代理可以缓存数据,控制调用次数。 代理在访问大文件和图时也很有用,可以延迟加载,缓存。
5. 代码实现
先看一种普遍写法:
/**
* 抽象主题,定义主要功能
*/
public interface Subject {
void operate();
}
/**
* 具体主题
*/
public class RealSubject implements Subject{
@Override
public void operate() {
System.out.println("realsubject operate started......");
}
}
/**
* 代理类
*/
publicclass Proxy implements Subject{
private Subject subject;
public Proxy(Subject subject) {
this.subject = subject;
}
@Override
publicvoid operate() {
System.out.println("before operate......");
subject.operate();
System.out.println("after operate......");
}
}
/**
* 客户
*/
public class Client {
public static void main(String[] args) {
Subject subject = new RealSubject();
Proxy proxy = new Proxy(subject);
proxy.operate();
}
}
这种实现方式可以体现一些代理的特性,但是,有些地方写的不好。client构造RealSubject对象,再通过RealSubject对象构造ProxySubject对象,这种逻辑不复合代理模式思想。代理模式的思想是代理对象控制对真实对象的访问,如果已经把真实对象暴露给外部就没有必要使用代理模式了,我觉得应该改造下,代理对象持用的真实对象的引用不应该通过构造方法传入,可以考虑通过工厂模式创建或者Spring注入的方式比较合理。
再看另一个例子,实现一个读取文件的代理: Design Patterns - Proxy Pattern
public interface Image {
void display();
}
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);
}
}
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();
}
}
public class ProxyPatternDemo {
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();
}
}
输出结果:
Loading test_10mb.jpg
Displaying test_10mb.jpg
Displaying test_10mb.jpg
ProxyImage代理用来减少RealImage对象加载时的内存访问代价。