代理模式是一种结构型设计模式,提供一个替代或占位符以控制对原对象的访问。代理充当原对象的接口,客户端通过代理间接与原对象交互,使得可以在访问对象前后添加额外的逻辑。
代理模式是一种为其他对象提供一种替代品或占位符的设计模式。代理对象控制着对原对象的访问,并允许在将请求提交给原对象前后执行一些操作。 代理通常被用于延迟加载、访问控制、日志记录、智能引用等场景。代理模式遵循接口隔离原则,客户端无需知晓代理与真实对象的区别,因为它们实现相同的接口。
代理模式的核心是创建一个新的代理类,它实现与原目标对象相同的接口,并持有一个指向目标对象的引用。当客户端调用代理对象的方法时,代理对象会在转发调用前后执行额外的逻辑。 这种模式允许在不修改原有代码的情况下,通过组合方式扩展目标对象的功能,符合开闭原则。代理对象可以完全控制何时以及如何将请求传递给目标对象,甚至可以决定是否传递。
当加载大型资源(如图片或视频)时,可以使用代理延迟加载,直到真正需要显示这些资源时才加载。
// 定义图片加载接口
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)已加载完成
保护代理控制对原始对象的访问,通常用于实现权限验证和访问控制。
// 文件接口
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: 管理员写入的内容
远程代理为位于不同地址空间的对象提供本地代表,隐藏了远程调用的复杂性。
# 定义服务接口
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
日志代理在方法调用前后添加日志记录,用于调试和性能监控。
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
特性 | 代理模式 | 装饰器模式 | 适配器模式 |
---|---|---|---|
主要目的 | 控制对对象的访问 | 动态添加功能 | 使不兼容接口能够协同工作 |
关系 | 代理与真实对象实现相同接口 | 装饰器与被装饰对象实现相同接口 | 适配器实现目标接口,包含被适配对象 |
对象创建 | 代理通常负责创建真实对象 | 装饰器通常接收已创建的对象 | 适配器接收已创建的对象 |
透明度 | 对客户端透明 | 对客户端透明 | 对客户端透明 |
典型应用 | 访问控制、延迟加载、缓存 | 添加功能、责任链 | 系统集成、兼容性处理 |
复杂度 | 中等 | 中等 | 低 |
可组合性 | 低(通常一个代理对应一个实例) | 高(可以嵌套多个装饰器) | 低(通常一个适配器对应一个实例) |
虽然代理模式和装饰器模式在结构上相似(都实现相同接口并包含对原对象的引用),但它们的意图不同:
代理模式确实会引入一定的间接性,可能导致轻微的性能开销。但在大多数情况下,这种开销是可以接受的,尤其是当代理提供的功能(如缓存、延迟加载)能够带来性能优势时。
需要注意的是:
当你需要在不修改原始对象的情况下控制对它的访问时,应该考虑使用代理模式。具体场景包括:
代理模式可以与多种设计模式结合使用,形成更强大的解决方案:
代理模式可能增加系统的复杂性,特别是当有多层代理时。以下是一些减轻复杂性的策略:
代理和真实对象应严格实现相同的接口,这样客户端才能透明地使用代理。代理不应该向客户端暴露不属于原始接口的特殊功能。
// 良好实践 interface Service { void operation(); } class RealService implements Service { public void operation() { /* ... */ } } class ServiceProxy implements Service { private RealService service; public void operation() { // 前置处理 service.operation(); // 后置处理 } }
除非明确需要预先初始化,否则应该在第一次请求时才创建真实对象,这可以节省资源并提高启动性能。
class LazyProxy implements Service { private RealService service = null; public void operation() { if (service == null) { service = new RealService(); // 懒初始化 } service.operation(); } }
在支持反射的语言中,使用动态代理机制可以减少手动创建代理类的工作量,特别是当有多个类需要相同类型的代理功能时。
// 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; } } );
代理应该正确处理和传递来自真实对象的异常,同时可以添加自己的异常处理逻辑,但不应隐藏原始异常信息。
public void operation() { try { // 前置处理 realService.operation(); // 后置处理 } catch (Exception e) { // 记录异常信息 logger.error("操作执行失败", e); // 重新抛出异常,保留原始异常信息 throw new ServiceException("服务调用失败", e); } }
代理应该有明确的目的,避免创建不必要的代理层。多层代理会增加复杂性和性能开销,考虑使用装饰器模式或责任链模式替代多层代理。
// 避免这样的多层代理 Client -> LoggingProxy -> SecurityProxy -> CachingProxy -> RealService // 考虑使用装饰器模式 Service service = new LoggingDecorator( new SecurityDecorator( new CachingDecorator( new RealService())));