代理模式入门:控制对象访问的优雅设计

今天我们来深入探讨代理模式(Proxy Pattern),一种结构型设计模式,用于通过代理对象控制对真实对象的访问。代理模式可以在不修改原有类的情况下添加额外的逻辑,如权限控制、延迟加载或日志记录。本文将带你实现一个简单的代理模式示例,适合初学者快速上手,同时为有经验的开发者提供进阶建议和优化思路。

代理模式在现实生活中类似中介,代理对象在客户端和真实对象之间扮演桥梁角色。本文使用 Java 语言,通过一个文件访问控制的场景展示代理模式的实现。让我们开始吧!

前置准备

在开始之前,确保开发环境已就绪:

  • JDK:推荐 JDK 17(也可使用 JDK 8+)。
  • IDE:IntelliJ IDEA、Eclipse 或 VS Code,推荐支持 Java 的 IDE。
  • 构建工具:Maven(可选,用于管理依赖)。
  • 项目结构:创建一个简单的 Java 项目,目录如下:
    proxy-pattern-demo
    ├── src
    │   ├── main
    │   │   ├── java
    │   │   │   └── com.example.proxy
    │   │   │       ├── subject
    │   │   │       ├── proxy
    │   │   │       └── Main.java
    │   └── test
    └── pom.xml
    

安装环境

  • 确保 JDK 已安装:java -version.
  • Maven(可选):mvn -version.
  • 无需额外依赖,本示例使用纯 Java。

步骤 1: 定义抽象主题

代理模式需要一个抽象主题接口,定义真实对象和代理的公共行为。在 com.example.proxy.subject.FileAccess 中:

package com.example.proxy.subject;

public interface FileAccess {
    void readFile(String fileName);
}

说明

  • FileAccess 是目标接口,定义文件读取操作。

步骤 2: 创建真实主题

实现具体的文件访问类。在 com.example.proxy.subject.RealFileAccess 中:

package com.example.proxy.subject;

public class RealFileAccess implements FileAccess {
    @Override
    public void readFile(String fileName) {
        System.out.println("Reading file: " + fileName);
    }
}

说明

  • RealFileAccess 是真实对象,执行实际的文件读取操作。

步骤 3: 创建代理类

创建代理类,控制对真实对象的访问并添加额外逻辑。在 com.example.proxy.proxy.FileAccessProxy 中:

package com.example.proxy.proxy;

import com.example.proxy.subject.FileAccess;
import com.example.proxy.subject.RealFileAccess;

public class FileAccessProxy implements FileAccess {
    private RealFileAccess realFileAccess;
    private final String userRole;

    public FileAccessProxy(String userRole) {
        this.userRole = userRole;
    }

    @Override
    public void readFile(String fileName) {
        if (!"admin".equalsIgnoreCase(userRole)) {
            System.out.println("Access denied for user with role: " + userRole);
            return;
        }
        
        if (realFileAccess == null) {
            realFileAccess = new RealFileAccess();
        }
        realFileAccess.readFile(fileName);
    }
}

说明

  • 代理类实现 FileAccess 接口,包装 RealFileAccess
  • 添加权限控制,仅允许 admin 角色访问。
  • 延迟加载:realFileAccess 在首次调用时初始化。

步骤 4: 客户端代码

com.example.proxy.Main 中测试代理模式:

package com.example.proxy;

import com.example.proxy.proxy.FileAccessProxy;
import com.example.proxy.subject.FileAccess;

public class Main {
    public static void main(String[] args) {
        // 非管理员用户
        FileAccess userAccess = new FileAccessProxy("user");
        userAccess.readFile("secret.txt");

        // 管理员用户
        FileAccess adminAccess = new FileAccessProxy("admin");
        adminAccess.readFile("secret.txt");
    }
}

运行输出

Access denied for user with role: user
Reading file: secret.txt

步骤 5: 运行和测试

  1. 编译和运行

    • 在 IDE 中运行 Main 类。
    • 或使用命令行:
      javac src/main/java/com/example/proxy/*.java src/main/java/com/example/proxy/*/*.java
      java com.example.proxy.Main
      
  2. 测试用例

    • 验证非管理员用户被拒绝访问。
    • 验证管理员用户成功读取文件。
    • 测试延迟加载:确认 RealFileAccess 未在代理初始化时创建。
  3. 调试技巧

    • 添加日志:使用 System.out 或 SLF4J 记录权限检查和调用过程。
    • 检查对象状态:在调试器中验证 realFileAccess 的延迟初始化。
    • 异常处理:在代理中添加输入验证(如文件名非空)。

进阶与最佳实践

  • 代理类型

    • 虚拟代理:如本示例的延迟加载。
    • 保护代理:本示例的权限控制。
    • 远程代理:用于远程服务调用(如 RMI)。
    • 动态代理:使用 Java 的 java.lang.reflect.Proxy
      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Proxy;
      
      public FileAccess createDynamicProxy(String userRole) {
          return (FileAccess) Proxy.newProxyInstance(
              FileAccess.class.getClassLoader(),
              new Class<?>[]{FileAccess.class},
              (proxy, method, args) -> {
                  if (!"admin".equalsIgnoreCase(userRole)) {
                      System.out.println("Access denied for user with role: " + userRole);
                      return null;
                  }
                  RealFileAccess real = new RealFileAccess();
                  return method.invoke(real, args);
              }
          );
      }
      
  • 异常处理

    • 添加自定义异常:
      public void readFile(String fileName) {
          if (fileName == null || fileName.isEmpty()) {
              throw new IllegalArgumentException("File name cannot be empty");
          }
          // ...
      }
      
  • 性能优化

    • 缓存权限检查结果:
      private boolean accessChecked = false;
      private boolean accessGranted = false;
      
      public void readFile(String fileName) {
          if (!accessChecked) {
              accessGranted = "admin".equalsIgnoreCase(userRole);
              accessChecked = true;
          }
          if (!accessGranted) {
              System.out.println("Access denied for user with role: " + userRole);
              return;
          }
          // ...
      }
      
  • 测试

    • 使用 JUnit 编写单元测试:
      import org.junit.Test;
      import static org.junit.Assert.*;
      
      public class FileAccessProxyTest {
          @Test
          public void testAdminAccess() {
              FileAccess proxy = new FileAccessProxy("admin");
              proxy.readFile("test.txt"); // 验证输出
          }
      
          @Test
          public void testUserAccess() {
              FileAccess proxy = new FileAccessProxy("user");
              proxy.readFile("test.txt"); // 验证拒绝
          }
      }
      
  • 其他应用场景

    • 日志记录:在代理中添加日志功能。
    • 缓存代理:缓存真实对象的结果。
    • AOP(面向切面编程):Spring AOP 使用动态代理实现。
  • 资源推荐:书籍《设计模式:可复用面向对象软件的基础》、Refactoring Guru 网站。多实践其他设计模式(如装饰者模式、观察者模式)。

总结

通过这个代理模式示例,你学会了如何通过代理控制对象访问,实现了权限管理和延迟加载。代理模式在需要控制访问、延迟初始化或添加横切关注点时非常实用,广泛应用于框架和分布式系统。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐