设计模式学习笔记(十五)代理模式

概念

代理模式是一种结构型设计模式,它允许通过创建一个代理对象来控制对另一个对象的访问。代理对象充当着客户端与目标对象之间的中介,可以在访问目标对象之前或之后添加额外的逻辑。代理模式主要用于控制、增强或隐藏目标对象的访问。

在这里插入图片描述
Subject(抽象主题角色):它声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。
Proxy(代理主题角色):它包含了对真实主题的引用,从而可以在任何时候操作真实主题对象;在代理主题角色中提供一个与真实主题角色相同的接口,以便在任何时候都可以替代真实主题;代理主题角色还可以控制对真实主题的使用,负责在需要的时候创建和删除真实主题对象,并对真实主题对象的使用加以约束。通常,在代理主题角色中,客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅仅是单纯调用真实主题对象中的操作。
RealSubject(真实主题角色):它定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。

代理模式是常用的结构型设计模式之一,当无法直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,所访问的真实对象与代理对象需要实现相同的接口。根据代理模式的使用目的不同,代理模式又可以分为多种类型,例如保护代理、远程代理、虚拟代理、缓冲代理等,它们应用于不同的场合,满足用户的不同需求。可以将代理模式比喻为我们找中介租房的过程。我们作为客户端需要租房,但是我们不想直接去找房东签订合同,而是找一个中介来代替我们与房东进行协商和签约。这个中介就是代理对象,房东就是目标对象。

通过以上的描述,个人感觉与适配器模式有点像,以下就是代理模式与适配器模式的区别:
一、目的不同:
代理模式(Proxy Pattern)的主要目的是控制对对象的访问,为其他对象提供一种代理以控制对这个对象的访问。
适配器模式(Adapter Pattern)的主要目的是将一个接口转换成另一个接口,使得原本由于接口不兼容而不能一起工作的类能够协同工作。

适用场景不同:
代理模式常用于需要对目标对象进行控制、增强或隐藏的情况。例如,远程代理(Remote Proxy)、虚拟代理(Virtual Proxy)和安全代理(Protection Proxy)等。
适配器模式常用于系统已存在的类或接口之间存在不兼容的情况,通过适配器可以让它们能够协同工作。例如,类适配器和对象适配器等。
关注点不同:

代理模式关注的是控制对目标对象的访问,可以在目标对象的方法执行前后增加额外的处理逻辑。
适配器模式关注的是接口的兼容性,将不兼容的接口转换为兼容的接口,使得原本无法一起工作的类能够协同工作。

实现方式不同:
代理模式一般通过创建一个具有相同接口的代理对象来包装目标对象,客户端通过代理对象来访问目标对象。
适配器模式可以有两种实现方式:类适配器和对象适配器。类适配器使用继承关系实现适配,对象适配器使用组合关系实现适配。

代理模式的设计非常复杂,下面就是几种常用的代理模式
1、远程代理(Remote Proxy):为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又称为大使(Ambassador)。
2、虚拟代理(Virtual Proxy):它延迟创建和初始化昂贵或耗时的对象,直到真正需要时才进行创建。虚拟代理在访问对象时充当了一个占位符,可以控制对象的实例化过程并提供对目标对象的间接访问。
3、 保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
4、 缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
5、智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。

示例

示例一 保护代理模式

我们在平时查询数据时,客户端可以直接通过接口调用数据,但现在有一个新需求,客户端在查询之前需要进行身份验证以及日志记录的操作。
在这里插入图片描述

/**
 * 抽象主题角色 用来查询数据的接口
 * */
abstract class SelectData {
    public abstract void selectByUser(String user);
}

//真实主题角色 继承查询类
class SelectDataImpl extends SelectData{

    @Override
    public void selectByUser(String user) {
        System.out.println("查询id为:" + user + "的数据成功");
    }
}


//身份验证类
class Auth {
    public boolean validate(String user) {
        if (user.equals("张三")) {
            return true;
        }else {
            return false;
        }
    }
}
//记录日志类
class Log {
    public void log(String user) {
        System.out.println("用户:" + user + "查询数据");
    }
}

//代理类:此代理将查询数据之前进行身份验证、记录日志操作
class Proxy extends SelectData{
    //真实查询数据的类
    private SelectDataImpl select = new SelectDataImpl();
    //身份验证类
    private Auth auth = new Auth();
    //日志类
    private Log log = new Log();
    @Override
    public void selectByUser(String user) {
        if (auth.validate(user)) {
            log.log(user);
            select.selectByUser(user);
        }else {
            System.out.println("身份验证失败,不存在用户:" + user);
        }
    }
}

客户端

		//使用代理查询数据
        SelectData selectData = new Proxy();
        selectData.selectByUser("张三");
        selectData.selectByUser("李四");

输出结果

用户:张三查询数据
查询id为:张三的数据成功
身份验证失败,不存在用户:李四

远程代理模式

远程代理(Remote Proxy)是代理设计模式的一种变体,它允许在不同的地址空间中通过代理对象访问或操作远程对象。远程代理隐藏了对象存在于不同地址空间的细节,使得客户端无需关心对象的具体位置,通过代理对象即可进行远程通信。

远程代理常见的应用场景是在分布式系统中进行远程方法调用(Remote Method Invocation,RMI)。客户端通过调用本地代理对象的方法,实际上是触发了远程代理对象在远程服务器上执行相应的方法,并将结果返回给客户端。

在远程代理模式中,代理对象起到了两个主要的作用:
隐藏复杂的网络通信细节:代理对象封装了底层的网络通信和远程调用的细节,使得客户端可以像访问本地对象一样访问远程对象,无需关心网络通信协议、数据传输等复杂性。
控制和管理远程对象的访问:代理对象可以实现一些额外的逻辑,如权限验证、缓存、负载均衡等,以控制和管理对远程对象的访问。

远程代理的使用能带来以下好处:
分布式系统的支持:远程代理使得分布在不同服务器上的对象可以通过代理进行通信和协作,实现分布式系统的构建。
隐藏网络细节:客户端无需了解对象位于远程服务器上的事实,只需要通过代理对象进行访问,减少了对网络通信细节的关注。
提高性能和资源利用:远程代理可以根据需求将请求发送到适合处理的服务器上,以提高系统的性能和资源利用率。
需要注意的是,在使用远程代理时,要确保网络通信的稳定性、数据传输的安全性,并对可能的错误进行处理和容错。同时,还需要考虑对象一致性和数据一致性的问题,以避免因为网络问题导致的数据不一致或错误。

虚拟代理模式

虚拟代理(Virtual Proxy)是代理设计模式的一种变体,它延迟创建和初始化真实对象,只在需要时才真正创建和使用它。虚拟代理在访问真实对象之前,可以进行一些额外的操作,例如加载大量数据、创建昂贵的对象或进行权限验证等。

虚拟代理通常用于以下情况:
惰性加载(Lazy Loading):虚拟代理允许在需要的时候延迟加载对象,而不是一开始就立即加载。这对于涉及大量数据或昂贵的资源的操作特别有用,可以减少启动时间并提高性能。
缓存(Caching):虚拟代理可以缓存对于真实对象的请求结果,当下次请求相同的数据时,直接返回缓存中的结果,避免重复计算或访问慢速资源。
访问控制(Access Control):虚拟代理可以在访问真实对象之前进行权限验证,确保用户有合适的权限才能访问。

缓冲代理模式

缓冲代理(Caching Proxy)是代理设计模式的一种变体,它在访问真实对象之前或之后,通过缓存中间结果来提高系统性能和响应速度。缓冲代理将某些操作的结果保存在缓存中,当下次需要相同操作结果时,直接从缓存中返回,而无需再次执行相同的操作。

缓冲代理常见的应用场景是对于计算开销较大的操作或频繁访问的数据,通过缓存中间结果,避免重复计算或访问慢速资源,提升系统性能和响应速度。
以下是一个示例,展示了如何使用缓冲代理来实现对于计算斐波那契数列的缓存:

import java.util.HashMap;
import java.util.Map;

// 计算接口
interface Calculator {
    long calculateFibonacci(int n);
}

// 真实计算类
class RealCalculator implements Calculator {
    @Override
    public long calculateFibonacci(int n) {
        if (n <= 1) {
            return n;
        }
        return calculateFibonacci(n - 1) + calculateFibonacci(n - 2);
    }
}

// 缓冲代理类
class CachingCalculator implements Calculator {
    private final Calculator calculator;
    private final Map<Integer, Long> cache;

    public CachingCalculator() {
        calculator = new RealCalculator();
        cache = new HashMap<>();
    }

    @Override
    public long calculateFibonacci(int n) {
        if (cache.containsKey(n)) {
            // 从缓存中获取结果
            return cache.get(n);
        } else {
            // 计算结果并保存到缓存中
            long result = calculator.calculateFibonacci(n);
            cache.put(n, result);
            return result;
        }
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        Calculator calculator = new CachingCalculator();

        // 第一次计算,会执行真实计算并保存结果到缓存
        long result1 = calculator.calculateFibonacci(10);
        System.out.println("Result 1: " + result1);

        // 第二次计算,直接从缓存中获取结果
        long result2 = calculator.calculateFibonacci(10);
        System.out.println("Result 2: " + result2);
    }
}

JDK动态代理

首先定义接口和目标实现类

//教师接口
public interface TeacherDao {
    void teach();
    String sayHello(String name);
}
//教师实现类,集成教师接口
public class TeacherImpl implements TeacherDao{
    @Override
    public void teach() {
        System.out.println("教学...");
    }

    @Override
    public String sayHello(String name) {
        return name;
    }
}

定义动态代理工厂

public class ProxyFactory {

    //维护的目标对象
    private Object target;
    //构造器初始化时,初始化目标对象target
    public ProxyFactory(Object target) {
        this.target = target;
    }
    //给目标对象生成一个代理对象
    public Object getProxyInstance() {
        /**
         * public static Object newProxyInstance(ClassLoader loader,
         *                                           Class<?>[] interfaces,
         *                                           InvocationHandler h)
         * 1、ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法固定
         * 2、Class<?>[] interfaces:目标对象实现的接口类型,使用泛型的方式确认类型
         * 3、InvocationHandler h:事件处理,执行目标对象方法时,会触发事件处理器方法,会把当前执行的目标对象作为参数传入
         * */
        ClassLoader loader = target.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler h = (proxy, method, args) -> {
            System.out.println("JDK代理开始...");
            //通过反射机制调用目标对象的方法
            Object returnVal = method.invoke(target, args);
            System.out.println("JDK代理提交;");
            return returnVal;
        };

        return Proxy.newProxyInstance(loader,interfaces,h);
    }
}

客户端

//创建目标对象
        TeacherDao teacher = new TeacherImpl();
        //给目标对象创建代理对象,返回的代理对象可以强转为
        TeacherDao proxyTeacherInstance = (TeacherDao) new ProxyFactory(teacher).getProxyInstance();
        //proxyTeacherInstance=class com.sun.proxy.$Proxy0 内存中动态生成了代理对象
        System.out.println("proxyTeacherInstance=" + proxyTeacherInstance.getClass());
        //通过代理对象,调用目标对象的方法
        proxyTeacherInstance.teach();
        System.out.println(proxyTeacherInstance.sayHello("james"));
输出结果:
proxyTeacherInstance=class com.sun.proxy.$Proxy0
JDK代理开始...
教学...
JDK代理提交...
JDK代理开始...
JDK代理提交...
james

注意:之前的静态代理,该代理类需要集成目标接口,并且在客户端使用的时候,需要自己new一个代理对象。而 动态代理是将目标类交给jdk通过反射来返回一个代理对象。
当代理对象调用目标对象的方法时,是通过InvocationHandler来监听到该请求。InvocationHandler接口的invoke方法是动态代理调用目标方法的入口。当你调用代理对象的方法时,实际上是 通过invoke方法来执行目标方法的。
在invoke方法中,通过method参数可以获取到被调用的方法对象,包括方法名、参数类型等。这样就可以根据需要来对不同的方法进行处理或监听。
因此,在示例代码中,当你调用代理对象的teach()方法时,method参数会传递该方法的相关信息给invoke方法。然后在invoke方法中,我们可以通过method对象来判断当前调用的是哪个方法,进而执行相应的逻辑。

总结

优点:
1、降低耦合度:代理模式将客户端与真实对象解耦,客户端只需与代理进行交互,无需直接与真实对象交互,减少了彼此之间的依赖关系。
2、增强安全性:代理可以在访问真实对象之前进行权限验证和安全检查,确保只有合法的请求才能访问真实对象。
3、延迟加载:代理模式可以实现延迟加载,即在需要时才创建和初始化真实对象,从而提高了系统的启动性能。
4、附加功能:代理对象可以在调用真实对象的方法前后附加额外的逻辑和功能,例如日志记录、缓存、性能监测等。

缺点:
1、增加复杂性:引入代理对象会增加系统的复杂性,需要创建和维护额外的代理类和相关代码。
2、增加请求层级:代理模式会引入一层额外的间接层,可能导致请求的处理效率降低。
3、部分功能受限:某些代理模式的实现方式可能无法完全替代真实对象,导致部分功能不可用。

适用场景:
1、远程访问:当需要通过网络或远程服务访问对象时,代理模式可以隐藏远程通信的细节,提供更便捷和安全的访问方式。
2、延迟加载:当创建对象较为耗时,且在实际使用时并不总是需要对象时,代理模式可以实现延迟加载,提高系统的性能和资源利用率。
3、访问控制:代理模式可以用于实现权限控制机制,确保只有具有合法权限的请求才能访问真实对象。
4、缓存数据:当需要缓存频繁访问的数据,避免重复计算或访问慢速资源时,代理模式可以提供数据缓存的功能。
5、动态代理:当需要在运行时动态地创建代理对象,并对请求进行动态处理时,可以使用动态代理。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值