【设计模式与体系结构】结构型模式-代理模式
简介
代理模式(Proxy Pattern)是一种结构型设计模式。它为其他对象提供一种代理,以控制对这个对象的访问。简单来说,当客户端不方便直接访问一个对象,或者需要在访问这个对象之前或之后执行一些额外的操作时,就可以使用代理对象来代替目标对象进行操作。
代理模式的角色
- 抽象主题角色Subject:声明具体主题和代理主题的共同接口,这样一来任何使用具体主题的地方都可以使用代理主题,客户端需要遵循依赖倒转原则,即面向抽象编程
- 具体主题角色ConcreteSubject:实现抽象主题中声明的接口方法
- 代理主题角色Proxy:包含对具体主题的引用,从而可以在任何时候操作具体主题对象,在代理主题角色中提供一个与具体主题角色相同的接口,以便在任何时候都可以替代具体主题;代理主题角色还可以控制对具体主题的使用,负责在需要的时候创建和删除具体主题对象,并对具体主题对象的使用加以约束。
代理模式的类型
- 远程代理(Remote Proxy):当需要访问位于远程服务器上的对象时使用,它使得客户端可以像访问本地对象一样访问远程对象。
- 虚拟代理(Virtual Proxy):当创建一个对象的成本很高,或者对象的创建很耗时,但又不是立即需要这个对象的全部功能时使用。
- 保护代理(Protection Proxy):当需要对目标对象进行访问控制,根据不同的用户权限提供不同程度的访问时使用。
- 智能代理(Smart Proxy):当需要在访问目标对象的过程中添加一些额外的智能行为,如引用计数、自动垃圾回收等情况时使用。
- 缓冲代理(Buffer Proxy):当对象会被频繁访问相同资源时,可以使用缓冲代理,对数据进行缓冲,从而提高数据的访问效率。
记忆口诀:想要办代理,只需元宝换(智虚远保缓)。
代理模式的优点
- 增强安全性:通过保护代理,可以对目标对象进行权限控制,防止未经授权的访问,保护系统的安全性和数据的完整性。
- 提高性能:利用虚拟代理可以延迟对象的创建,避免不必要的资源消耗。例如,在网页加载图片的场景中,避免了一次性加载大量高分辨率图片导致的性能问题。
- 降低复杂度:远程代理可以隐藏远程对象访问的网络通信等复杂细节,使得客户端的代码更加简洁,易于维护和理解。
代理模式的缺点
- 增加系统复杂度:引入代理对象会增加代码的复杂性。因为需要同时维护代理对象和真实对象,以及它们之间的关系。如果代理的逻辑比较复杂,可能会导致代码难以理解和调试。
- 降低性能(部分情况):如果代理对象的额外操作(如权限验证等)过于复杂或者频繁,可能会对系统性能产生一定的影响。不过,在合理使用的情况下,这种影响通常是可以接受的,并且可以通过优化代理的操作来减轻这种影响。
代理模式的使用场景
- 当客户端对象需要访问远程主机中的对象时可以使用远程代理。
- 当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时可以使用虚拟代理,例如一个对象需要很长时间才能完成加载时。
- 当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时可以使用缓冲代理。通过使用缓冲代理,系统无须在客户端每一次访问时都重新执行操作,只需直接从临时缓冲区获取操作结果即可。
- 当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时可以使用保护代理。
- 当需要为一个对象的访问(引用)提供一些额外的操作时可以使用智能引用代理。
正文
虚拟代理(Virtual Proxy)
虚拟代理(Virtual Proxy)可以延迟一个资源消耗大的对象的创建,从而达到提高性能的作用。例如一家餐厅里面顾客一般是先由服务员接待,而不是直接由厨师来接待,只有当顾客向服务员点餐完成,服务员才将做菜的任务交由厨师完成。
定义一个餐厅工作人员接口 ICook.java
public interface ICook {
void cook();//做菜
void add(String delicacies);//点菜
}
定义一个厨师类 Cook.java
public class Cook implements ICook {
private List<String> list = new LinkedList<String>();
@Override
public void cook() {
for (String s: list) {
System.out.println("做了一道菜:" + s);
}
list.removeAll(list);
}
@Override
public void add(String delicacies) {
System.out.println("厨师在菜单上加了一道菜:" + delicacies);
list.add(delicacies);
}
}
定义一个服务员类 Waiter.java
public class Waiter implements ICook {
private Cook cook;
private List<String> list = new LinkedList<String>();
@Override
public void cook() {
System.out.println("服务员把做菜的任务转交给了厨师");
if (cook == null) cook = new Cook();
for (String s: list) {
cook.add(s);
}
list.removeAll(list);
cook.cook();
}
@Override
public void add(String delicacies) {
if (cook != null) {
cook.add(delicacies);
} else {
System.out.println("服务员在菜单上加了一道菜:" + delicacies);
list.add(delicacies);
}
}
}
最后写一个消费者类 Consumer.java,调用服务员类的虚拟代理。
public class Consumer {
public static void main(String[] args) {
//定义一个服务员对象
ICook waiter = new Waiter();
//顾客向服务员点菜,服务员先自己记录
waiter.add("红烧黄河大鲤鱼");
waiter.add("鲶鱼炖茄子");
waiter.add("佛跳墙");
waiter.add("北京烤鸭");
waiter.add("荔浦芋头炒肉");
//服务员记录完菜单,自然是要进入到做菜环境,但是服务员不负责做菜,而是要转交给厨师负责
waiter.cook();
//顾客加菜,由于厨师对象已经实例化了,因此加菜的任务直接交给厨师即可
waiter.add("蔬菜沙拉");
waiter.add("水果拼盘");
//厨师把加的菜做了
waiter.cook();
}
}
运行效果截图如下:

保护代理(Protection Proxy)
保护代理(Protection Proxy)可以对不同用户提供不同程度的访问。例如公司里的普通员工没有发布公告的权限,但是老板具有发布公告的权限。
定义一个员工接口 Employee.java
public interface Employee {
void publish(String text);
}
定义一个员工实体类 EmployeeImpl.java
public class EmployeeImpl implements Employee {
//定义一个发布公告的方法,明显普通员工没有发布公告的权限,高层员工有发布公告的权限
@Override
public void publish(String text) {
System.out.println("发布公告:" + text);
}
}
Proxy类:Java反射包自带,其中newProxyInstance可以返回接口实现类的实例。因此可以使用Proxy类来反射到员工实体类的方法调用,以此做出功能的保护。
定义一个普通员工的引用类 EmployeeInvocationHandler.java
public class EmployeeInvocationHandler implements InvocationHandler {
private Employee employee;
public EmployeeInvocationHandler(Employee employee) {
this.employee = employee;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("publish".equals(method.getName())) {
throw new IllegalAccessException("普通员工不能发布公告");
}
return method.invoke(employee, args);
}
}
定义一个高层员工的引用类 EmployerInvocationHandler.java
public class EmployerInvocationHandler implements InvocationHandler {
private Employee employee;
public EmployerInvocationHandler(Employee employee) {
this.employee = employee;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("publish".equals(method.getName())) {
return method.invoke(employee, args);
}
return null;
}
}
定义一个客户端 Client.java
public class Client {
public static void main(String[] args) {
Employee employee1 = new EmployeeImpl();
Employee employee2 = new EmployeeImpl();
Employee employeeProxy2 = getEmployerProxy(employee2);
employeeProxy2.publish("bbbb");
Employee employeeProxy1 = getEmployeeProxy(employee1);
employeeProxy1.publish("aaaa");
}
private static Employee getEmployeeProxy(Employee employee) {
return (Employee) Proxy.newProxyInstance(employee.getClass().getClassLoader(),
employee.getClass().getInterfaces(),
new EmployeeInvocationHandler(employee));
}
private static Employee getEmployerProxy(Employee employee) {
return (Employee) Proxy.newProxyInstance(employee.getClass().getClassLoader(),
employee.getClass().getInterfaces(),
new EmployerInvocationHandler(employee));
}
}
运行效果截图如下:

总结
与中介者模式、外观模式的区别
答:代理模式是代理一个子系统的部分功能;外观模式是集中子系统的功能组合,对外提供一致的使用接口;中介者模式是针对子系统内部模块之间,通过一个中介者简化各对象间的相互引用。
浙公网安备 33010602011771号