代理模式 (Proxy Pattern)

代理模式是一种结构型设计模式,提供一个替代或占位符以控制对原对象的访问。代理充当原对象的接口,客户端通过代理间接与原对象交互,使得可以在访问对象前后添加额外的逻辑。

详细定义

代理模式是一种为其他对象提供一种替代品或占位符的设计模式。代理对象控制着对原对象的访问,并允许在将请求提交给原对象前后执行一些操作。 代理通常被用于延迟加载、访问控制、日志记录、智能引用等场景。代理模式遵循接口隔离原则,客户端无需知晓代理与真实对象的区别,因为它们实现相同的接口。

核心概念图

classDiagram Client --> Subject Subject <|.. RealSubject Subject <|.. Proxy Proxy --> RealSubject class Subject { +request() [Interface] } class RealSubject { +request() } class Proxy { -realSubject: RealSubject +request() } class Client { +request() }

实现细节

代理模式的核心是创建一个新的代理类,它实现与原目标对象相同的接口,并持有一个指向目标对象的引用。当客户端调用代理对象的方法时,代理对象会在转发调用前后执行额外的逻辑。 这种模式允许在不修改原有代码的情况下,通过组合方式扩展目标对象的功能,符合开闭原则。代理对象可以完全控制何时以及如何将请求传递给目标对象,甚至可以决定是否传递。

核心组件及功能
  • Subject (主题接口) - 定义了 RealSubject 和 Proxy 的共同接口,这样在任何使用 RealSubject 的地方都可以使用 Proxy
  • RealSubject (真实主题) - 定义了代理所代表的实际对象,是最终要引用的对象
  • Proxy (代理) - 保存一个引用使得代理可以访问实际对象,提供与 Subject 接口相同的接口,这样代理就可以用来替代实际对象
  • Client (客户端) - 通过代理间接访问目标对象,无需感知代理的存在

代理模式工作流程

graph TD A[客户端] -->|请求| B[代理对象] B -->|前置处理| C{是否继续?} C -->|是| D[真实对象] C -->|否| E[拒绝/返回缓存] D -->|执行实际操作| F[返回结果] F -->|后置处理| G[最终结果] E -->|返回结果| G G -->|响应| A

代理模式结构

classDiagram Client --> Subject Subject <|.. RealSubject Subject <|.. Proxy Proxy --> RealSubject class Subject { +request() [Interface] } class RealSubject { +request() } class Proxy { -realSubject: RealSubject +request() } class Client { +request() }

应用场景

1. 虚拟代理 - 延迟加载

当加载大型资源(如图片或视频)时,可以使用代理延迟加载,直到真正需要显示这些资源时才加载。


// 定义图片加载接口
class ImageLoader {
  loadImage(url) {
    throw new Error('抽象方法未实现');
  }
}

// 真实图片加载器
class RealImageLoader extends ImageLoader {
  constructor() {
    super();
  }

  loadImage(url) {
    console.log(`实际加载图片: ${url}`);
    // 模拟图片加载
    return `图片(${url})已加载完成`;
  }
}

// 图片加载代理
class ImageLoaderProxy extends ImageLoader {
  constructor() {
    super();
    this.realLoader = new RealImageLoader();
    this.cache = {};
  }

  loadImage(url) {
    // 检查是否已缓存
    if (this.cache[url]) {
      console.log(`从缓存返回图片: ${url}`);
      return this.cache[url];
    }
  
    // 首次加载时使用真实加载器
    console.log(`首次加载,使用代理`);
    const result = this.realLoader.loadImage(url);
    this.cache[url] = result;
    return result;
  }
}

// 客户端使用
const imageProxy = new ImageLoaderProxy();
console.log(imageProxy.loadImage('example.jpg'));
console.log(imageProxy.loadImage('example.jpg')); // 从缓存加载

运行结果:

首次加载,使用代理
实际加载图片: example.jpg
图片(example.jpg)已加载完成
从缓存返回图片: example.jpg
图片(example.jpg)已加载完成
                

2. 保护代理 - 访问控制

保护代理控制对原始对象的访问,通常用于实现权限验证和访问控制。


// 文件接口
interface FileAccess {
    String readFile(String fileName);
    void writeFile(String fileName, String content);
}

// 真实文件系统
class RealFileSystem implements FileAccess {
    @Override
    public String readFile(String fileName) {
        return "文件内容: " + fileName;
    }

    @Override
    public void writeFile(String fileName, String content) {
        System.out.println("写入文件 " + fileName + ": " + content);
    }
}

// 文件访问代理
class FileAccessProxy implements FileAccess {
    private RealFileSystem realFileSystem;
    private String userRole;

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

    @Override
    public String readFile(String fileName) {
        // 所有用户都可以读取文件
        System.out.println("代理: 允许用户 [" + userRole + "] 读取文件");
        return realFileSystem.readFile(fileName);
    }

    @Override
    public void writeFile(String fileName, String content) {
        // 只有管理员可以写入文件
        if ("admin".equals(userRole)) {
            System.out.println("代理: 允许管理员写入文件");
            realFileSystem.writeFile(fileName, content);
        } else {
            System.out.println("代理: 拒绝用户 [" + userRole + "] 写入文件,权限不足");
        }
    }
}

// 使用示例
public class ProxyDemo {
    public static void main(String[] args) {
      // 普通用户
FileAccess userProxy = new FileAccessProxy("user");
System.out.println(userProxy.readFile("data.txt"));
userProxy.writeFile("data.txt", "新内容"); // 将被拒绝

// 管理员
FileAccess adminProxy = new FileAccessProxy("admin");
System.out.println(adminProxy.readFile("data.txt"));
adminProxy.writeFile("data.txt", "管理员写入的内容"); // 允许
    }
}

运行结果:

代理: 允许用户 [user] 读取文件
文件内容: data.txt
代理: 拒绝用户 [user] 写入文件,权限不足
代理: 允许用户 [admin] 读取文件
文件内容: data.txt
代理: 允许管理员写入文件
写入文件 data.txt: 管理员写入的内容
                

3. 远程代理 - 远程服务调用

远程代理为位于不同地址空间的对象提供本地代表,隐藏了远程调用的复杂性。


# 定义服务接口
class WeatherService:
    def get_weather(self, city):
        pass

# 模拟远程服务
class RemoteWeatherService(WeatherService):
    def get_weather(self, city):
        # 实际中这里会调用远程API
        print(f"远程服务: 获取{city}的天气数据")
        return f"{city}天气: 晴朗, 25°C"

# 远程代理
class WeatherServiceProxy(WeatherService):
    def __init__(self):
        self.remote_service = RemoteWeatherService()
        self.cache = 
        self.cache_time = 
        self.cache_duration = 3600  # 缓存1小时
    
    def get_weather(self, city):
        import time
        current_time = time.time()
        
        # 检查缓存是否有效
        if city in self.cache and current_time - self.cache_time.get(city, 0) < self.cache_duration:
            print(f"代理: 从缓存返回{city}的天气数据")
            return self.cache[city]
        
        # 缓存无效,调用远程服务
        print(f"代理: 缓存过期,调用远程服务")
        result = self.remote_service.get_weather(city)
        
        # 更新缓存
        self.cache[city] = result
        self.cache_time[city] = current_time
        
        return result

# 客户端使用
def client_code():
    weather_proxy = WeatherServiceProxy()
    
    # 第一次调用,会请求远程服务
    print(weather_proxy.get_weather("北京"))
    
    # 短时间内再次调用,将使用缓存
    print(weather_proxy.get_weather("北京"))
    
    # 请求不同城市的数据
    print(weather_proxy.get_weather("上海"))

# 运行客户端代码
client_code()

运行结果:

代理: 缓存过期,调用远程服务
远程服务: 获取北京的天气数据
北京天气: 晴朗, 25°C
代理: 从缓存返回北京的天气数据
北京天气: 晴朗, 25°C
代理: 缓存过期,调用远程服务
远程服务: 获取上海的天气数据
上海天气: 晴朗, 25°C
                

4. 日志代理 - 方法调用记录

日志代理在方法调用前后添加日志记录,用于调试和性能监控。


using System;
using System.Diagnostics;

// 定义服务接口
public interface IUserService
{
    void CreateUser(string username);
    string GetUserDetails(int userId);
}

// 实际服务实现
public class UserService : IUserService
{
    public void CreateUser(string username)
    {
        // 模拟创建用户的操作
        Console.WriteLine($"创建用户: {username}");
        // 模拟耗时操作
        System.Threading.Thread.Sleep(100);
    }

    public string GetUserDetails(int userId)
    {
        // 模拟获取用户详情
        Console.WriteLine($"获取用户ID: {userId} 的详情");
        // 模拟耗时操作
        System.Threading.Thread.Sleep(50);
        return $"用户 {userId}: 张三, 年龄: 30";
    }
}

// 日志代理
public class LoggingUserServiceProxy : IUserService
{
    private readonly IUserService _service;
    
    public LoggingUserServiceProxy(IUserService service)
    {
        _service = service;
    }
    
    public void CreateUser(string username)
    {
        Console.WriteLine($"[日志] 开始调用 CreateUser, 参数: {username}");
        
        var stopwatch = Stopwatch.StartNew();
        try
        {
            _service.CreateUser(username);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"[日志] 异常: {ex.Message}");
            throw;
        }
        finally
        {
            stopwatch.Stop();
            Console.WriteLine($"[日志] 结束调用 CreateUser, 耗时: {stopwatch.ElapsedMilliseconds}ms");
        }
    }
    
    public string GetUserDetails(int userId)
    {
        Console.WriteLine($"[日志] 开始调用 GetUserDetails, 参数: {userId}");
        
        var stopwatch = Stopwatch.StartNew();
        try
        {
            string result = _service.GetUserDetails(userId);
            return result;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"[日志] 异常: {ex.Message}");
            throw;
        }
        finally
        {
            stopwatch.Stop();
            Console.WriteLine($"[日志] 结束调用 GetUserDetails, 耗时: {stopwatch.ElapsedMilliseconds}ms");
        }
    }
}

// 客户端代码
public class Program
{
    public static void Main()
    {
        IUserService service = new LoggingUserServiceProxy(new UserService());
        
        service.CreateUser("alice");
        string details = service.GetUserDetails(123);
        Console.WriteLine($"获取到的用户详情: {details}");
    }
}

运行结果:

[日志] 开始调用 CreateUser, 参数: alice
创建用户: alice
[日志] 结束调用 CreateUser, 耗时: 103ms
[日志] 开始调用 GetUserDetails, 参数: 123
获取用户ID: 123 的详情
[日志] 结束调用 GetUserDetails, 耗时: 52ms
获取到的用户详情: 用户 123: 张三, 年龄: 30
                

比较分析

特性 代理模式 装饰器模式 适配器模式
主要目的 控制对对象的访问 动态添加功能 使不兼容接口能够协同工作
关系 代理与真实对象实现相同接口 装饰器与被装饰对象实现相同接口 适配器实现目标接口,包含被适配对象
对象创建 代理通常负责创建真实对象 装饰器通常接收已创建的对象 适配器接收已创建的对象
透明度 对客户端透明 对客户端透明 对客户端透明
典型应用 访问控制、延迟加载、缓存 添加功能、责任链 系统集成、兼容性处理
复杂度 中等 中等
可组合性 低(通常一个代理对应一个实例) 高(可以嵌套多个装饰器) 低(通常一个适配器对应一个实例)

各模式适用性评分 (1-5)

代理模式

访问控制:
5/5
延迟加载:
5/5
功能扩展:
3/5
接口转换:
1/5

装饰器模式

访问控制:
3/5
延迟加载:
1/5
功能扩展:
5/5
接口转换:
1/5

适配器模式

访问控制:
1/5
延迟加载:
1/5
功能扩展:
2/5
接口转换:
5/5

设计模式选择决策树

graph TD A[需要修改对象行为?] -->|是| B[是否需要控制访问?] A -->|否| Z[考虑其他模式] B -->|是| C[代理模式] B -->|否| D[是否需要动态添加功能?] D -->|是| E[装饰器模式] D -->|否| F[是否需要接口转换?] F -->|是| G[适配器模式] F -->|否| H[考虑其他模式] C -->|访问控制| C1[保护代理] C -->|延迟初始化| C2[虚拟代理] C -->|远程调用| C3[远程代理] C -->|缓存结果| C4[缓存代理]

总结

代理模式思维导图

mindmap root((代理模式)) 类型 虚拟代理 延迟加载 按需创建 保护代理 访问控制 权限验证 远程代理 远程服务调用 分布式系统 缓存代理 结果缓存 性能优化 智能引用 引用计数 资源管理 核心组件 Subject接口 RealSubject Proxy Client 优点 控制访问 延迟加载 透明性 关注点分离 缺点 增加复杂度 性能开销 间接性 应用场景 图片加载 权限控制 远程服务 日志记录

常见问题解答

代理模式和装饰器模式有什么区别?

虽然代理模式和装饰器模式在结构上相似(都实现相同接口并包含对原对象的引用),但它们的意图不同

  • 代理模式主要关注控制对对象的访问,代理通常自行管理其服务对象的生命周期
  • 装饰器模式主要关注动态添加功能,不控制对象访问,而是扩展对象功能
  • 代理通常在对象生命周期开始时就创建,而装饰器可以在运行时动态添加
  • 装饰器可以嵌套使用,而代理通常不会嵌套
代理模式会影响性能吗?

代理模式确实会引入一定的间接性,可能导致轻微的性能开销。但在大多数情况下,这种开销是可以接受的,尤其是当代理提供的功能(如缓存、延迟加载)能够带来性能优势时。

需要注意的是:

  • 虚拟代理和缓存代理实际上通常会提高系统性能
  • 保护代理和日志代理可能会增加一些开销,但通常微不足道
  • 远程代理中的网络延迟通常远大于代理本身引入的开销
什么时候应该使用代理模式?

当你需要在不修改原始对象的情况下控制对它的访问时,应该考虑使用代理模式。具体场景包括:

  • 需要延迟初始化昂贵对象(虚拟代理)
  • 需要基于权限或条件控制访问(保护代理)
  • 需要在本地代表远程对象(远程代理)
  • 需要缓存操作结果以提高性能(缓存代理)
  • 需要在访问对象前后添加行为,如日志记录(日志代理)
代理模式如何与其他设计模式结合使用?

代理模式可以与多种设计模式结合使用,形成更强大的解决方案:

  • 代理 + 工厂模式:工厂可以根据条件返回真实对象或其代理
  • 代理 + 单例模式:代理可以确保只有一个真实对象实例被创建
  • 代理 + 观察者模式:代理可以在对象状态变化时通知观察者
  • 代理 + 装饰器模式:先用代理控制访问,再用装饰器添加功能
  • 代理 + 适配器模式:代理可以包含适配器,在控制访问的同时转换接口
如何避免代理模式导致的代码复杂性?

代理模式可能增加系统的复杂性,特别是当有多层代理时。以下是一些减轻复杂性的策略:

  • 使用动态代理(如Java的JDK动态代理或CGLIB)自动生成代理类
  • 使用面向切面编程(AOP)实现横切关注点,如日志和安全
  • 保持代理类职责单一,避免在一个代理中混合多种功能
  • 为复杂代理提供清晰文档,说明其目的和行为
  • 考虑使用装饰器模式替代多层代理

最佳实践

1. 保持接口一致性

代理和真实对象应严格实现相同的接口,这样客户端才能透明地使用代理。代理不应该向客户端暴露不属于原始接口的特殊功能。

// 良好实践
interface Service {
    void operation();
}

class RealService implements Service {
    public void operation() { /* ... */ }
}

class ServiceProxy implements Service {
    private RealService service;
    
    public void operation() {
        // 前置处理
        service.operation();
        // 后置处理
    }
}
                

2. 懒初始化真实对象

除非明确需要预先初始化,否则应该在第一次请求时才创建真实对象,这可以节省资源并提高启动性能。

class LazyProxy implements Service {
    private RealService service = null;
    
    public void operation() {
        if (service == null) {
            service = new RealService(); // 懒初始化
        }
        service.operation();
    }
}
                

3. 利用动态代理减少样板代码

在支持反射的语言中,使用动态代理机制可以减少手动创建代理类的工作量,特别是当有多个类需要相同类型的代理功能时。

// Java示例
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 创建动态代理
Service service = (Service) Proxy.newProxyInstance(
    Service.class.getClassLoader(),
    new Class[] { Service.class },
    new InvocationHandler() {
        private final Service realService = new RealService();
        
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) 
            throws Throwable {
            System.out.println("Before method: " + method.getName());
            Object result = method.invoke(realService, args);
            System.out.println("After method: " + method.getName());
            return result;
        }
    }
);
                

4. 合理处理异常传递

代理应该正确处理和传递来自真实对象的异常,同时可以添加自己的异常处理逻辑,但不应隐藏原始异常信息。

public void operation() {
    try {
        // 前置处理
        realService.operation();
        // 后置处理
    } catch (Exception e) {
        // 记录异常信息
        logger.error("操作执行失败", e);
        // 重新抛出异常,保留原始异常信息
        throw new ServiceException("服务调用失败", e);
    }
}
                

5. 避免过度使用代理

代理应该有明确的目的,避免创建不必要的代理层。多层代理会增加复杂性和性能开销,考虑使用装饰器模式或责任链模式替代多层代理。

// 避免这样的多层代理
Client -> LoggingProxy -> SecurityProxy -> CachingProxy -> RealService

// 考虑使用装饰器模式
Service service = new LoggingDecorator(
                      new SecurityDecorator(
                          new CachingDecorator(
                              new RealService())));
                

参考资料