代理模式 (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. 代理模式通常与工厂模式一起使用,由工厂来创建代理对象
相关推荐
weixin_403810131 天前
EasyClick 安卓自动化版本 如何自激活代理模式并且启动安卓的自动化服务
android·自动化·代理模式
亲爱的非洲野猪2 天前
2动态规划进阶:背包问题详解与实战
算法·动态规划·代理模式
亲爱的非洲野猪2 天前
动态规划进阶:树形DP深度解析
算法·动态规划·代理模式
apolloyhl6 天前
Proxy 代理模式
代理模式
爱编码的傅同学6 天前
【今日算法】LeetCode 5.最长回文子串 和 287.寻找重复数
算法·leetcode·代理模式
玄冥剑尊7 天前
动态规划入门
算法·动态规划·代理模式
蜜汁小强9 天前
macOS 上的git代理配置在哪里
git·macos·代理模式·proxy模式
小码过河.9 天前
设计模式——代理模式
设计模式·代理模式
Engineer邓祥浩9 天前
设计模式学习(14) 23-12 代理模式
学习·设计模式·代理模式