【设计模式与体系结构】结构型模式-代理模式
简介
代理模式(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)); } }
运行效果截图如下:
总结
与中介者模式、外观模式的区别
答:代理模式是代理一个子系统的部分功能;外观模式是集中子系统的功能组合,对外提供一致的使用接口;中介者模式是针对子系统内部模块之间,通过一个中介者简化各对象间的相互引用。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 【.NET】调用本地 Deepseek 模型