二十三种设计模式-代理模式

时间:2025-01-18 06:59:37

一、定义与核心思想

代理模式是一种结构型设计模式,其核心思想是提供一个代理对象,用以控制对实际对象的访问。通过代理对象,可以在不改变实际对象的情况下,添加各种功能,如权限控制、懒加载、缓存、远程调用等。

二、组成要素

代理模式主要由以下几个要素组成:

  1. 抽象主题(Subject)

    • 这是一个接口或抽象类,定义了代理对象和实际对象共同的接口,使得代理对象可以在客户端中替代实际对象使用。

    • 例如,定义一个Subject接口,其中声明了request()方法。

  2. 实际主题(RealSubject)

    • 实现抽象主题接口的具体类,定义了代理所代表的真实对象。实际主题对象实现了业务逻辑,是客户端最终需要调用的对象。

    • 例如,RealSubject类实现了Subject接口,在request()方法中实现了具体的业务逻辑。

  3. 代理(Proxy)

    • 也实现了抽象主题接口,内部包含一个对实际主题对象的引用。代理对象在调用实际主题对象的方法前后可以添加额外的逻辑,从而控制对实际主题对象的访问。

    • 例如,Proxy类实现了Subject接口,并且有一个RealSubject类型的成员变量。在request()方法中,代理对象可以进行权限检查、日志记录等操作,然后调用实际主题对象的request()方法。

三、实现示例

以下是使用Java语言实现代理模式的一个简单示例:

// 抽象主题接口
interface Subject {
    void request();
}

// 实际主题类
class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("实际对象的请求");
    }
}

// 代理类
class Proxy implements Subject {
    private RealSubject realSubject;

    @Override
    public void request() {
        preRequest();
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        realSubject.request();
        postRequest();
    }

    private void preRequest() {
        System.out.println("代理对象的预处理操作,如权限检查");
    }

    private void postRequest() {
        System.out.println("代理对象的后处理操作,如日志记录");
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        Subject proxy = new Proxy(); // 直接创建代理对象
        proxy.request(); // 通过代理对象调用方法
    }
}

在这个示例中,客户端通过代理对象proxy调用request()方法。代理对象在调用实际主题对象RealSubjectrequest()方法前后,分别进行了预处理和后处理操作,如权限检查和日志记录。

四、优点

  1. 职责分离

    • 代理对象可以将客户端与实际主题对象解耦,使得客户端不需要直接与实际主题对象交互,从而降低了系统的耦合度。

  2. 功能扩展

    • 代理对象可以在不修改实际主题对象的情况下,添加各种功能,如权限控制、缓存、懒加载等,符合开闭原则。

  3. 控制访问

    • 代理对象可以控制对实际主题对象的访问,例如,根据用户的权限决定是否允许调用实际主题对象的方法。

  4. 远程调用

    • 代理对象可以处理远程方法调用的细节,如网络通信、序列化/反序列化等,使得客户端可以透明地调用远程对象的方法。

  5. 延迟加载

    • 代理对象可以实现懒加载,只有在第一次调用方法时才创建实际主题对象,从而提高系统的性能。

五、缺点

  1. 增加复杂性

    • 代理模式会增加系统的复杂性,因为需要创建额外的代理类和管理代理对象与实际对象之间的关系。

  2. 性能开销

    • 代理对象的调用会增加额外的性能开销,特别是当代理对象的逻辑比较复杂时,可能会对系统的性能产生一定的影响。

六、应用场景

  1. 权限控制

    • 通过代理对象可以控制对实际对象的访问权限,例如,根据用户的权限决定是否允许调用某个方法或访问某个资源。

    • 例如,实现一个权限控制代理,只有具备相应权限的用户才能通过代理对象调用实际对象的方法。

  2. 远程调用

    • 代理对象可以处理远程方法调用的细节,使得客户端可以透明地调用远程服务器上的方法。

    • 例如,使用RMI(Remote Method Invocation)技术实现远程对象调用,客户端通过代理对象调用远程服务器上的方法。

  3. 懒加载

    • 代理对象可以实现懒加载,只有在第一次调用方法时才创建实际对象,从而提高系统的性能。

    • 例如,加载一个大型的数据库连接对象时,可以使用懒加载代理来延迟创建连接对象,直到真正需要使用时才创建。

  4. 缓存

    • 代理对象可以缓存方法的调用结果,当再次调用相同的方法时,直接返回缓存的结果,从而提高系统的性能。

    • 例如,缓存网页的静态内容,当用户再次请求相同的内容时,直接从缓存中获取,而不需要重新生成。

  5. 日志记录

    • 代理对象可以在调用实际对象的方法前后记录日志,用于监控和调试。

    • 例如,记录方法的调用时间、参数、返回值等信息,帮助开发者进行问题定位和性能优化。

七、动态代理

除了静态代理,Java还支持动态代理。动态代理可以在运行时动态创建代理对象,而不需要在编译时定义代理类。动态代理主要使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现。

动态代理示例
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 抽象主题接口
interface Subject {
    void request();
}

// 实际主题类
class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("实际对象的请求");
    }
}

// 调用处理器
class MyInvocationHandler implements InvocationHandler {
    private Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        preRequest();
        Object result = method.invoke(target, args);
        postRequest();
        return result;
    }

    private void preRequest() {
        System.out.println("代理对象的预处理操作,如权限检查");
    }

    private void postRequest() {
        System.out.println("代理对象的后处理操作,如日志记录");
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        Subject realSubject = new RealSubject(); // 创建实际主题对象
        MyInvocationHandler handler = new MyInvocationHandler(realSubject); // 创建调用处理器并传入实际主题对象

        Subject proxy = (Subject) Proxy.newProxyInstance(
            Subject.class.getClassLoader(), // 目标类加载器
            new Class<?>[]{Subject.class}, // 目标类实现的接口
            handler // 调用处理器
        );

        proxy.request(); // 通过代理对象调用方法
    }
}

在这个示例中,客户端通过Proxy.newProxyInstance方法动态创建了一个代理对象proxy。代理对象的调用处理器MyInvocationHandler在调用实际主题对象RealSubject的方法前后,分别进行了预处理和后处理操作。

总结

代理模式通过代理对象控制对实际对象的访问,可以在不修改实际对象的情况下,添加各种功能,如权限控制、懒加载、缓存、远程调用等。代理模式在实际开发中非常有用,选择静态代理还是动态代理取决于具体的需求和场景。