定义
provide a surrogate or placeholder for another object to control access to it.
为一个对象提供一种代理,以控制对该对象的访问。
实列
生活中,有很多类似于信用卡、律师、车票代售点、网游加速器这样的对象,它们可以代表真实对象与另一个对象交互。
比如说,代售火车票的携程网站,它可以代表12306向乘客售卖火车票,同时它又可以对原有的购票功能进行增强如抢票功能;
再比如,网络加速器,未安装它之前我们是直接访问目标服务器的,安装之后我们的网络会连接到加速服务器上,之后访问目标服务器时都是由加速服务器转发的,
之所以要间接访问是因为加速服务器访问目标服务器的速度比我们本地的网络更快。
所以,可以看出,代理对象不仅可以代表原始对象,还能对原始对象的能力进行增强。
故事
不久前,因同事离职我接手了他负责的支付系统,同时又发现订单系统会因网络异常时常收不到支付系统的通知。
所以,领导让我赶紧解决一下这个问题。在我了解前任开发的代码时,
发现Order对象是订单组的同事封装好的网络请求不能擅自修改。又因我对该系统不太熟悉,又怕大的结构变化导致系统不稳定,所以只能简单修改原先的代码来扩展新功能。
伪代码如下:
public class Payment {
protected Order order;
public void payed(){
//...其它逻辑
//通知订单系统
//原先逻辑:order.notify(data);
try {
order.notify(data);
}catch (Exception e){
order.notify(data);
}
//...其它逻辑
}
}
问题
故事中,因订单对象不可修改,为了给通知功能增加重试行为,我修改了原来的代码。
如果后续我们要为通知功能再附加异步、日志记录、鉴权等行为,那么便会出现不断修改支付类的现象。
甚至我们希望这些行为可以在运行时,动态的添加或移除,那又怎么办呢?
比如说,在支付系统的访问压力不大的时候,我们希望通知是同步的,但在压力大的时候我们希望它是异步的。
所以,有没有一种方式可以不修改代码的前提下,为功能附加新的行为以及让这些行为可以在运行时动态配置,这便是代理模式。
方案
代理模式通过一个代理对象,将客户端访问的主题对象(目标对象)包装起来,使客户端通过代理对象来访问主题对象,从而可以在访问主题对象前后为其扩展新的行为。
代理对象之所以可以代表主题对象与客户端交互,是因为它和主题对象实现了一个共同接口,正因如此,主题对象可以存在多个形成链式结构的代理对象。
这种链式的层层包装结构,可以很好的支持主题对象行为的扩展,而且能支持运行时动态地移除或新增代理对象。
比如说,当我们要给已有重试代理的订单对象扩展异步行为时,那么只需继承目标接口并且包装重试代理对象,就能实现附加行为的扩展。
实现
接下来,我们使用代理模式实现一下故事中的程序。
首先,我们应该有一个主题接口,它是主题对象和代理对象的共同接口。
/**主题接口*/
public interface IOrder {
public void notify(String data);
}
然后,我们创建一个单独的重试订单代理类,将主题接口类型的对象和重试行为封装在这个类中。
public class RetryOrderProxy implements IOrder{
private final IOrder iOrder;
public RetryOrderProxy(IOrder iOrder){
this.iOrder = iOrder;
}
@Override
public void notify(String data) {
try {
iOrder.notify(data);
}catch (NetworkException e){
iOrder.notify(data);
}
}
}
现在,我们还可以创建一个异步订单代理类,将主题接口类型的对象和异步行为封装在这个类中
/**异步订单代理类*/
public class AsyncOrderProxy implements IOrder {
private final IOrder iOrder;
public AsyncOrderProxy(IOrder iOrder){
this.iOrder = iOrder;
}
@Override
public void notify(String data) {
new Thread(new Runnable() {
@Override
public void run() {
iOrder.notify(data);
}
}).start();
}
}
最后,让我们演示一下如何给通知功能附加新的行为重试、异步。
public class Client {
public static void main(String[] args) {
Order order = new Order();
RetryOrderProxy retryProxy = new RetryOrderProxy(order);
AsyncOrderProxy asyncProxy = new AsyncOrderProxy(retryProxy);
asyncProxy.notify(args[0]);
}
}
结构

抽象主题角色(Subject):它负责声明真实主题和代理对象需要实现的共同接口。
代理角色(Proxy):它负责控制客户对真实对象的访问,在访问前后对真实对象的行为进行扩展。它持有一个指向真实主题的引用。
真实主题角色(RealSubject):这是客户通过代理真实访问的对象,它委托代理来接受客户端的访问。
应用
日志代理(Logging Proxy):在传递客户请求的前后,对请求信息(参数、地址、响应)进行记录。如:切面日志可以帮助我们统一记录所有Controller的请求参数及其返回数据。
远程代理(Remote Proxy):它以透明的方式将客户的请求转发到另一台服务器上,使客户端仅通过本地方法调用就可以访问另一台服务器上的方法。
如:Dubbo,Spring Cloud等微服务框架都使用了远程代理技术。
缓存代理(Caching Proxy):当客户访问资源时,如果该资源在缓存中就不再请求真实对象,而是直接从缓存获取资源并返回。
保护代理(Protection Proxy):类似于火车站的检票机,它在传递客户请求的前后,对请求进行访问控制如:身份认证、限流、熔断等等,从而保护了资源访问的安全性。
总结
当给一个对象附加额外的行为且对象不可修改时,为了避免代码的修改,应该考虑使用代理模式在对象的访问前后对其行为进行扩展。
这样,可以增加系统的扩展性,灵活性,以及复用性。
网友评论