代理模式 (Proxy Pattern)

代理模式 (Proxy Pattern)

概述

代理模式是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

意图

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

适用场景

  • 远程代理:为一个对象在不同的地址空间提供局部代表
  • 虚拟代理:根据需要创建开销很大的对象
  • 保护代理:控制对原始对象的访问
  • 智能指引:取代了简单的指针,它在访问对象时执行一些附加操作

结构

复制代码
┌─────────────┐          ┌─────────────┐
│   Client    │──────────>│   Proxy     │
├─────────────┤          ├─────────────┤
│             │          │ - realSubject│
└─────────────┘          │ + request() │
                         └─────────────┘
                                 ▲
                                 │
┌─────────────┐          ┌─────────────┐
│   Subject   │<─────────│RealSubject │
├─────────────┤          ├─────────────┤
│ + request() │          │ + request() │
└─────────────┘          └─────────────┘

参与者

  • Subject:定义RealSubject和Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy
  • RealSubject:定义Proxy所代表的真实实体
  • Proxy:保存一个引用使得代理可以访问实体,并提供一个与Subject的接口相同的接口,这样代理就可以用来替代实体

示例代码

下面是一个完整的代理模式示例,以图片加载为例:

java 复制代码
// Subject - 主题接口
public interface Image {
    void display();
}

// RealSubject - 真实主题
public class RealImage implements Image {
    private String fileName;
    
    public RealImage(String fileName) {
        this.fileName = fileName;
        loadFromDisk(fileName);
    }
    
    @Override
    public void display() {
        System.out.println("显示图片: " + fileName);
    }
    
    private void loadFromDisk(String fileName) {
        System.out.println("从磁盘加载图片: " + fileName);
    }
}

// Proxy - 代理类
public class ProxyImage implements Image {
    private RealImage realImage;
    private String fileName;
    
    public ProxyImage(String fileName) {
        this.fileName = fileName;
    }
    
    @Override
    public void display() {
        if (realImage == null) {
            realImage = new RealImage(fileName);
        }
        realImage.display();
    }
}

// Client - 客户端
public class Client {
    public static void main(String[] args) {
        System.out.println("从代理加载图片");
        
        Image image1 = new ProxyImage("image1.jpg");
        Image image2 = new ProxyImage("image2.jpg");
        
        // 图片将从磁盘加载
        image1.display();
        System.out.println("");
        
        // 图片不需要从磁盘加载
        image1.display();
        System.out.println("");
        
        // 图片将从磁盘加载
        image2.display();
        System.out.println("");
        
        // 图片不需要从磁盘加载
        image2.display();
    }
}

另一个示例 - 保护代理

java 复制代码
// Subject - 主题接口
public interface BankAccount {
    void deposit(double amount);
    void withdraw(double amount);
    double getBalance();
}

// RealSubject - 真实主题
public class RealBankAccount implements BankAccount {
    private String accountNumber;
    private double balance;
    private String owner;
    
    public RealBankAccount(String accountNumber, String owner, double initialBalance) {
        this.accountNumber = accountNumber;
        this.owner = owner;
        this.balance = initialBalance;
    }
    
    @Override
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            System.out.println("存款成功,存入金额: " + amount);
        } else {
            System.out.println("存款金额必须大于0");
        }
    }
    
    @Override
    public void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            System.out.println("取款成功,取出金额: " + amount);
        } else if (amount > balance) {
            System.out.println("余额不足");
        } else {
            System.out.println("取款金额必须大于0");
        }
    }
    
    @Override
    public double getBalance() {
        return balance;
    }
    
    public String getOwner() {
        return owner;
    }
}

// Proxy - 代理类
public class BankAccountProxy implements BankAccount {
    private RealBankAccount realAccount;
    private String currentUser;
    
    public BankAccountProxy(String accountNumber, String owner, double initialBalance) {
        this.realAccount = new RealBankAccount(accountNumber, owner, initialBalance);
    }
    
    public void setCurrentUser(String user) {
        this.currentUser = user;
    }
    
    @Override
    public void deposit(double amount) {
        if (checkAccess()) {
            realAccount.deposit(amount);
        } else {
            System.out.println("无权进行存款操作");
        }
    }
    
    @Override
    public void withdraw(double amount) {
        if (checkAccess()) {
            realAccount.withdraw(amount);
        } else {
            System.out.println("无权进行取款操作");
        }
    }
    
    @Override
    public double getBalance() {
        if (checkAccess()) {
            return realAccount.getBalance();
        } else {
            System.out.println("无权查看余额");
            return 0;
        }
    }
    
    private boolean checkAccess() {
        // 简单的权限检查,只有账户所有者才能访问
        return currentUser != null && currentUser.equals(realAccount.getOwner());
    }
}

// Client - 客户端
public class Client {
    public static void main(String[] args) {
        // 创建银行账户代理
        BankAccount account = new BankAccountProxy("123456789", "张三", 1000.0);
        
        // 设置当前用户为账户所有者
        ((BankAccountProxy) account).setCurrentUser("张三");
        
        System.out.println("当前用户: 张三");
        account.deposit(500.0);
        account.withdraw(200.0);
        System.out.println("余额: " + account.getBalance());
        
        System.out.println();
        
        // 设置当前用户为非账户所有者
        ((BankAccountProxy) account).setCurrentUser("李四");
        
        System.out.println("当前用户: 李四");
        account.deposit(500.0);
        account.withdraw(200.0);
        System.out.println("余额: " + account.getBalance());
    }
}

动态代理示例

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// Subject - 主题接口
public interface UserService {
    void addUser(String username, String password);
    void deleteUser(String username);
    String getUser(String username);
}

// RealSubject - 真实主题
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username, String password) {
        System.out.println("添加用户: " + username);
    }
    
    @Override
    public void deleteUser(String username) {
        System.out.println("删除用户: " + username);
    }
    
    @Override
    public String getUser(String username) {
        System.out.println("获取用户: " + username);
        return "User: " + username;
    }
}

// InvocationHandler - 调用处理器
public class LogInvocationHandler implements InvocationHandler {
    private Object target;
    
    public LogInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 记录方法调用前的日志
        System.out.println("调用方法: " + method.getName() + ", 参数: " + java.util.Arrays.toString(args));
        
        // 调用目标方法
        Object result = method.invoke(target, args);
        
        // 记录方法调用后的日志
        System.out.println("方法 " + method.getName() + " 执行完成");
        
        return result;
    }
}

// Client - 客户端
public class Client {
    public static void main(String[] args) {
        // 创建真实对象
        UserService userService = new UserServiceImpl();
        
        // 创建动态代理
        UserService proxy = (UserService) Proxy.newProxyInstance(
            userService.getClass().getClassLoader(),
            userService.getClass().getInterfaces(),
            new LogInvocationHandler(userService)
        );
        
        // 使用代理
        proxy.addUser("张三", "123456");
        proxy.getUser("张三");
        proxy.deleteUser("张三");
    }
}

优缺点

优点

  1. 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度
  2. 远程代理使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求
  3. 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度
  4. 保护代理可以控制对真实对象的使用权限

缺点

  1. 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢
  2. 实现代理模式需要额外的工作,有些代理模式的实现非常复杂

相关模式

  • 适配器模式:适配器模式改变对象的接口,而代理模式不改变对象的接口
  • 装饰器模式:装饰器模式增强对象的功能,而代理模式控制对对象的访问
  • 外观模式:外观模式简化接口,而代理模式保持接口不变

实际应用

  • Java中的RMI(远程方法调用)
  • Spring框架中的AOP(面向切面编程)
  • Hibernate中的懒加载
  • Java中的动态代理
  • 防火墙代理
  • 缓存代理

静态代理与动态代理

  • 静态代理:在编译时就已经确定代理类和被代理类的关系,需要为每个被代理类创建一个代理类
  • 动态代理:在运行时动态生成代理类,不需要为每个被代理类创建代理类,更加灵活

Java中的动态代理主要通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。

代理模式的类型

  1. 远程代理:为一个位于不同地址空间的对象提供一个本地代理
  2. 虚拟代理:根据需要创建开销很大的对象,通过代理来延迟对象的创建
  3. 保护代理:控制对原始对象的访问,用于对象有不同访问权限的时候
  4. 智能引用:取代了简单的指针,它在访问对象时执行一些附加操作,如计算引用次数等

注意事项

  1. 代理模式和装饰器模式的结构相似,但目的不同。代理模式控制访问,装饰器模式增加功能
  2. 代理模式可能会增加系统的复杂性,特别是在动态代理中
  3. 在使用代理模式时,需要权衡性能和功能之间的关系
  4. 代理模式通常与工厂模式一起使用,由工厂来创建代理对象
相关推荐
ydd10010011 天前
正则表达式匹配
正则表达式·代理模式
LayZhangStrive13 天前
设计模式(二)装饰器模式、代理模式(待更新)
设计模式·代理模式·装饰器模式·后端开发
Lam Tang18 天前
APS 系列文章10
java·代理模式
Lam Tang18 天前
APS 系列文章 08
java·代理模式
wangjialelele18 天前
动态规划DP经典题型总结(Java、C++):路径、子数组、子序列、背包问题详解
java·c++·算法·面试·动态规划·代理模式
聪明蛋子哟18 天前
从MCP到A2A再到Proxy:Agent-工具协议的三种代理模式
代理模式
Lam Tang18 天前
APS 系列文章 09
java·代理模式
Lam Tang19 天前
APS 系列文章 01
java·代理模式
只吃不吃香菜24 天前
Windows 下基于 YAML 规则的指定域名分流:以哔哩哔哩为例
网络·网络协议·计算机网络·网络安全·代理模式
zhongjunyao1 个月前
Spec-Driven Development (SDD): The Definitive 2026 Guide
javascript·spring boot·python·eclipse·代理模式