524e9a97a641fac8488736a561e44157
5分钟理解设计模式 —— 代理模式

[TOC]

概述:

  5分钟理解设计模式系列,将通过解决实际问题,来带您理解设计模式,本文希望带您搞懂的3个问题是:
1.代理模式的原理与实现?
2.动态代理的原理与实现?
3.代理模式的应用场景?

1.代理模式的原理与实现?

代理模式(Proxy Design Pattern),指在不改变被代理类代码的情况下,通过引入代理类来为被代理类提供附加功能。
代理模式是一种比较贴近于生活的设计模式,举一个实际生活中的例子:
住酒店不一定需要亲自到酒店去,还可以通过微信支付下的同程艺龙来订酒店。
这里【同程艺龙】就在不改变【酒店】的情况下,扩展了其在线订房的功能。
接下来我们通过一段代码来解释一下代理模式的定义:
现在我们有这样一个需求,记录用户登陆所用时间,所以这里我们设计两个Controller,分别用于实现用户登陆以及记录时间的功能。

/**
 * 模拟记录方法耗时.
 *
 * jialin.li
 * 2020-02-25 23:53
 */
public class MetricsCollector {
    private long usedTime;

    public void setUsedTime(long usedTime) {
        this.usedTime = usedTime;
    }
}
/**
 * 模拟用户登陆.
 *
 * jialin.li
 * 2020-02-25 23:55
 */
public class UserController {

    private MetricsCollector metricsCollector = new MetricsCollector();

    public void login(String telephone, String password) {
        long startTimestamp = System.currentTimeMillis();
        // ... 省略login逻辑...
        long endTimeStamp = System.currentTimeMillis();
        long responseTime = endTimeStamp - startTimestamp;
        metricsCollector.setUsedTime(responseTime);
    }
}

这样就完成了需求,但是这样做有没有什么问题?
我们将记录方法耗时的代码称为框架代码,这部分与业务无关。用户登陆的代码称为业务代码。
此时框架代码和业务代码是紧耦合在一起的,这样做就会有2个问题
1 如果有一天框架代码需要替换成其他框架,那么就需要对每一处使用该框架的代码进行修改。
2 框架代码和业务代码写在一个类中,此时这个类不符合单一职责原则。
这个时候我们就可以使用代理模式来解决这个问题,而代理模式,又有两种实现方法。

1 组合

为了解决上述问题,这个时候就可以使用代理模式。代理类 UserControllerProxy 和原始类 UserController 实现相同的接口 IUserController。UserController 类只负责业务功能。代理类 UserControllerProxy 负责在业务代码执行前后附加其他逻辑代码,并通过委托的方式调用原始类来执行业务代码。具体的代码实现如下所示:

/**
 * 模拟用户登陆接口.
 *
 * jialin.li
 * 2020-02-25 23:55
 */
public interface IUserController {
    void login(String telephone, String password);
}
/**
 * 模拟用户登陆.
 *
 * jialin.li
 * 2020-02-25 23:55
 */
public class UserController implements IUserController{

    
    public void login(String telephone, String password) {
        // ... 省略login逻辑...
    }
}
/**
 * 模拟用户登陆代理类.
 *
 * jialin.li
 * 2020-02-26 00:06
 */
public class UserControllerProxy implements IUserController {

    private UserController userController = new UserController();
    private MetricsCollector metricsCollector = new MetricsCollector();

    
    public void login(String telephone, String password) {
        long startTimestamp = System.currentTimeMillis();
        userController.login(telephone, password);
        long endTimeStamp = System.currentTimeMillis();
        long responseTime = endTimeStamp - startTimestamp;
        metricsCollector.setUsedTime(responseTime);
    }
}

根据基于接口而非实现类的编程思想,这里在实现代理模式的时候,使代理类和被代理类实现了相同的接口。但是,如果原始类并没有定义接口,并且原始类代码来源于第三方类库,这个时候就没有办法通过这种方法来实现代理模式,这个时候就需要使用继承的方法实现代理模式。

2 继承

/**
 * 模拟用户登陆.
 *
 * jialin.li
 * 2020-02-25 23:55
 */
public class UserController{

    public void login(String telephone, String password) {
        // ... 省略login逻辑...
    }
}
/**
 * 模拟用户登陆代理类.
 *
 * jialin.li
 * 2020-02-26 00:06
 */
public class UserControllerProxy extends UserController {
    private MetricsCollector metricsCollector = new MetricsCollector();

    
    public void login(String telephone, String password) {
        long startTimestamp = System.currentTimeMillis();
        super.login(telephone, password);
        long endTimeStamp = System.currentTimeMillis();
        long responseTime = endTimeStamp - startTimestamp;
        metricsCollector.setUsedTime(responseTime);
    }
}

2.动态代理的原理与实现?

上面我们实现的代理模式,被称为静态代理。每一个代理类都对应着一个被代理类,那么在实际的开发过程中,如果我们需要扩展50个类的功能,那么至少就需要写50个代理类。这大大增加了代码的维护成本,这个问题就可以通过动态代理(Dynamic Proxy)来解决,我们不再去编写代理类,而是在程序运行的时候,去动态的创建代理类,然后在系统中用代理类替换掉被代理类,Java提供了动态代理的api(底层是反射),所以实现动态代理也比较容易。

public class MetricsCollectorProxy {
    private MetricsCollector metricsCollector = new MetricsCollector();

    public Object createProxy(Object proxiedObject) {
        Class<?>[] interfaces = proxiedObject.getClass().getInterfaces();
        DynamicProxyHandler handler = new DynamicProxyHandler(proxiedObject);
        return Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(), interfaces, handler);
    }

    private class DynamicProxyHandler implements InvocationHandler {
        private Object proxiedObject;

        public DynamicProxyHandler(Object proxiedObject) {
            this.proxiedObject = proxiedObject;
        }

        
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            long startTimestamp = System.currentTimeMillis();
            Object result = method.invoke(proxiedObject, args);
            long endTimeStamp = System.currentTimeMillis();
            long responseTime = endTimeStamp - startTimestamp;
            metricsCollector.setUsedTime(responseTime);
            return result;
        }
    }
}
//MetricsCollectorProxy使用举例
MetricsCollectorProxy proxy = new MetricsCollectorProxy();
UserController userController = (UserController) proxy.createProxy(new UserController());

Spring AOP底层就是基于动态代理,Spring会为被代理对象创建动态的代理对象,并且在JVM中替换原始类对象。

3.代理模式的应用场景?

被代理模式的应用场景主要有两种。
1.类似于demo中的在系统中添加一些非业务的功能,比如监控,日志,限流等等
2.RPC框架也可以看作是一种代理模式,它隐藏了网络通讯、编解码的细节。使得使用者可以专注于开发业务逻辑

最后,期待您的订阅和点赞,专栏每周都会更新,希望可以和您一起进步,同时也期待您的批评与指正!

© 著作权归作者所有
这个作品真棒,我要支持一下!
一个坚持原创的小专栏。分享编程知识,提升工作效率,致力于通过简单的语言,把编程这点事讲清楚。涵盖内容:java、设...
0条评论
top Created with Sketch.