设计模式——代理模式

创建型设计模式前面我们已经学完了,创建型模式主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码。

今天我们开始学习结构型设计模式,结构型设计模式主要总结了一些类或对象组合在一起的经典结构,这些经典结构可以解决特定应用场景的问题。

话不多说,我们从代理模式开始学起。

原理解析

代理模式:

  • 为其他对象提供了一种代理以控制对这个对象的访问。

  • 在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。

接下来,我们从一个例子来展现代理模式的实现原理:

系统需要记录用户关键操作日志,以便后期的系统维护,方便的查看问题,并及时排除.

我们需要建立一张日志记录的数据库表,并包含 id、 ip_addr、user_name、operation、 create_date 这些字段,分别对应 日志id、ip地址、用户名、操作、操作时间。

接下来我们模拟一下在 UserController 中的相关代码:

public class UserController {
    // 登录
    public void login(String username, String password){
        // 日志记录,里面的参数值都是获取到的,此处只是模拟
        Log log = new Log();
        log.setIpAddr("192.168.1.1");
        log.setUserName("小黑");
        log.setOperation("方法名:login " + "[参数1,类型:"+ username.getClass() + "值:" + username
                + " 参数2,类型:" + password.getClass() + "值:" + username);
        log.setCreateDate(new Date());

        // 省略存入数据库代码
        ...
        // 省略登录过程代码
        ...
    }
    // 注册
    public void register(String username, String password){
        Log log = new Log();
        log.setIpAddr("192.168.1.1");
        log.setUserName("小胖");
        log.setOperation("方法名:register " + "[参数1,类型:"+ username.getClass() + "值:" + username
                + " 参数2,类型:" + password.getClass() + "值:" + username);
        log.setCreateDate(new Date());

        // 省略存入数据库代码
        ...
        // 省略登录过程代码
        ...
    }
    
    // 登出
    ...
}

很明显,上面的写法存在问题。第一,日志收集代码侵入到业务代码中,跟业务代码高度耦合。如果未来要更改日志收集的逻辑或者参数,那替换成本会比较大。第二,收集日志信息的代码跟业务代码无关,本就不应该放到一个类中。业务类最好职责单一,只聚焦业务处理。

为了将非业务代码和业务代码解耦,代理模式就派上用场了。代理类 UserControllerProxy 和原始类 UserController 实现相同的接口 IUserController。UserController 类只负责业务功能。代理类 UserControllerProxy 负责在业务代码执行前后附加其他逻辑代码,并通过委托的方式调用调用原始类来执行业务代码。

public interface IUserController {

    void login(String username, String password);

    void register(String username, String password);

    void logout();

}

 
public class UserController implements IUserController {

    @Override
    public void login(String username, String password) {
        // 省略 login 逻辑...

    }

    @Override
    public void register(String username, String password) {
        // 省略 register 逻辑...
    }

    @Override
    public void logout() {
        // 省略 logout 逻辑...
    }

}


public class UserControllerProx implements IUserController {

    private UserController userController;

    public UserControllerProx(UserController userController) {
        this.userController = userController;
    }

    @Override
    public void login(String username, String password) {
        // 日志记录,里面的参数值都是获取到的,此处只是模拟
        Log log = new Log();
        log.setIpAddr("192.168.1.1");
        log.setUserName("小黑");
        log.setOperation("方法名:login " + "[参数1,类型:"+ username.getClass() + "值:" + username
                + " 参数2,类型:" + password.getClass() + "值:" + username);
        log.setCreateDate(new Date());
        // 委托
        userController.login(username, password);

    }

    @Override
    public void register(String userName, String password) {
        // 类似 login
    }

    @Override
    public void logout() {
        // 类似 login
    }
}

使用举例:

//因为原始类和代理类实现相同的接口,是基于接口而非实现编程
//将UserController类对象替换为UserControllerProxy类对象,不需要改动太多代码
IUserController  userController = new UserControllerProx(new UserController());

在上面的代码中,代理类和原始类需要实现相同的接口。但是,如果原始类并没有定义接口,并且原始类代码来自第三方类库,我们也没法直接修改原始类,给它重新定义一个接口。此时,我们可以采用继承的方式。

public class UserControllerProx extends UserController {
    @Override
    public void login(String username, String password) {
        // 日志记录,里面的参数值都是获取到的,此处只是模拟
        Log log = new Log();
        log.setIpAddr("192.168.1.1");
        log.setUserName("小黑");
        log.setOperation("方法名:login " + "[参数1,类型:"+ username.getClass() + "值:" + username
                + " 参数2,类型:" + password.getClass() + "值:" + username);
        log.setCreateDate(new Date());
        super.login(username, password);
    }

    @Override
    public void register(String username, String password) {
        // 类似 login...
        super.register(username, password);
    }

    @Override
    public void logout() {
        // 类似 login...
        super.logout();
    }
}

使用:

UserController userController = new UserControllerProxy();

动态代理

上面的代码还是存在一些问题,第一,我们需要在代理类中,将原始类中的所有的方法,都重新实现一遍,并且为每个方法都附加相似的代码逻辑。第二,如果要添加的附加功能的类有不止一个,我们需要对每个类都创建一个代理类会导致类的个数成倍增加。并且每个代理类中都存在大量的“重复”代码。

针对这个问题,我们就可以使用动态代理来解决。

动态代理: 不事先为每个原始类编写代理类,而是在运行的时候,动态的创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。

public class LogCollectorProx {

    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;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Log log = new Log();
            log.setIpAddr("192.168.1.1");
            log.setUserName("小黑");
            log.setOperation("方法名:"+ method.getName()); // 此处可通过反射获取更多参数信息
            log.setCreateDate(new Date());
            // 省略数据库操作 ...

            Object result = method.invoke(proxiedObject, args);
            return result;
        }
    }

}

使用举例:

LogCollectorProx prox = new LogCollectorProx();
IUserController userController = (IUserController) prox.createProxy(new UserController());

实际上,Spring AOP 底层的实现原理就是基于动态代理。

应用场景

代理模式常用在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等。我们将这些附加功能与业务功能解耦,放到代理类统一处理,让程序员只需要关注业务方面的开发。除此之外,代理模式还可以用在 RPC、缓存等应用场景中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值