代理模式(长文解析)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目:《Spring AI 项目实战》 正在持续爆肝中,基于 Spring AI + Spring Boot 3.x + JDK 21..., 点击查看 ;
- 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观
前言
在软件开发中,我们经常需要通过某种机制间接访问对象,以实现权限控制、延迟加载、网络通信等目标。代理模式(Proxy Pattern)正是为这类需求设计的结构型设计模式。它像一位“中间人”,在客户端与目标对象之间建立桥梁,既能保护核心对象的安全,又能提升系统的灵活性。本文将通过通俗易懂的比喻、分步讲解和代码示例,帮助读者掌握代理模式的核心思想和应用场景。
核心概念与定义
代理模式的核心思想是通过代理对象间接访问目标对象,其本质是“控制访问权限”和“增强功能”。可以将其想象为一个“门卫系统”:
- 门卫(代理对象):负责验证访客身份、记录访问日志、分发权限等。
- 房间(目标对象):存放着需要被访问的核心资源。
代理模式的三个关键角色如下:
- 目标接口(Target):定义客户端与真实对象共同遵守的接口。
- 真实主体(Real Subject):实际执行核心功能的对象。
- 代理(Proxy):实现目标接口,并持有对真实主体的引用,负责拦截和转发请求。
代码结构示例:
from abc import ABC, abstractmethod
class Image(ABC):
@abstractmethod
def display(self):
pass
class RealImage(Image):
def __init__(self, filename):
self.filename = filename
self.load_from_disk() # 模拟加载大图
def display(self):
print(f"Displaying {self.filename}")
class ProxyImage(Image):
def __init__(self, filename):
self.filename = filename
self.real_image = None # 延迟加载
def display(self):
if self.real_image is None:
self.real_image = RealImage(self.filename)
self.real_image.display()
代理模式的四大常见类型
代理模式根据业务场景可分为四种典型子类型,每种类型都像一把钥匙,对应不同的“控制访问”需求。
1. 远程代理(Remote Proxy)
作用:隐藏对象位于远程服务器的事实,使客户端像调用本地对象一样访问网络资源。
比喻:就像通过快递员(代理)从外地仓库(真实对象)取货,用户无需知道快递的具体流程。
案例场景:
class RemoteServiceProxy:
def __init__(self, host):
self.host = host
def call_api(self, endpoint):
# 模拟建立网络连接
print(f"Connecting to {self.host}...")
# 转发请求到真实服务
return RealRemoteService(self.host).execute(endpoint)
2. 虚拟代理(Virtual Proxy)
作用:延迟加载(Lazy Loading)大资源,提升系统性能。
比喻:如同“占位符”——先显示低分辨率缩略图,再加载高清大图。
代码实现:
class VirtualImageProxy(Image):
def __init__(self, filename):
self.filename = filename
self.thumbnail = "placeholder.jpg" # 占位图
def display(self):
print(f"Showing thumbnail: {self.thumbnail}")
# 仅在用户明确操作时加载真实图片
if user_clicks_view_full():
super().display()
3. 保护代理(Protection Proxy)
作用:控制对敏感资源的访问权限,实现权限验证。
比喻:像“门卫”——只有持有有效证件的人才能进入会议室。
权限验证示例:
class SecureFileProxy:
def __init__(self, file_path, user):
self.file_path = file_path
self.user = user
def read(self):
if self.user.has_permission("read"):
return RealFile(self.file_path).read()
else:
raise PermissionError("Access Denied")
4. 智能代理(Smart Proxy)
作用:在方法调用前后自动执行附加逻辑,如缓存、日志记录或性能监控。
比喻:像“智能助手”——在你拨打电话前自动记录通话时间,在通话后生成账单。
缓存优化案例:
class CachedServiceProxy:
def __init__(self, service):
self.service = service
self.cache = {}
def get_data(self, key):
if key in self.cache:
print("Returning cached data")
return self.cache[key]
else:
result = self.service.get_data(key)
self.cache[key] = result
return result
代理模式 vs 其他设计模式对比
与装饰器模式(Decorator Pattern)的异同
- 相同点:两者都使用组合(Composition)持有目标对象,并实现相同接口。
- 关键区别:
| 特征 | 代理模式 | 装饰器模式 |
|---------------|------------------------|--------------------------|
| 核心目标 | 控制访问权限 | 动态扩展功能 |
| 关注点 | 中介控制 | 功能增强 |
| 典型场景 | 权限验证、延迟加载 | 日志记录、性能监控 |
与适配器模式(Adapter Pattern)的差异
适配器模式解决“接口不兼容”问题,而代理模式解决“间接访问”需求。例如:
- 适配器:将圆形插头(旧接口)适配成方形插孔(新接口)。
- 代理:在插头与插座之间加装一个保护套(代理),防止过载。
典型应用场景与代码实践
场景1:网络请求的封装
// 远程代理在Java中的实现
public interface WeatherAPI {
String getTemperature(String city);
}
// 真实服务(模拟远程调用)
class RealWeatherAPI implements WeatherAPI {
@Override
public String getTemperature(String city) {
// 模拟网络延迟
try { Thread.sleep(1000); } catch (InterruptedException e) {}
return "25°C";
}
}
// 代理添加缓存功能
class CachingWeatherProxy implements WeatherAPI {
private final Map<String, String> cache = new HashMap<>();
private final WeatherAPI realAPI;
public CachingWeatherProxy(WeatherAPI realAPI) {
this.realAPI = realAPI;
}
@Override
public String getTemperature(String city) {
if (cache.containsKey(city)) {
return cache.get(city);
} else {
String result = realAPI.getTemperature(city);
cache.put(city, result);
return result;
}
}
}
场景2:图像加载优化
在移动应用开发中,使用虚拟代理避免一次性加载大量高分辨率图片:
class ImageView {
constructor(filename) {
this.filename = filename;
this.imageProxy = new ImageProxy(filename);
}
display() {
this.imageProxy.show();
}
}
class ImageProxy {
constructor(filename) {
this.filename = filename;
this.realImage = null;
}
show() {
if (!this.realImage) {
this.realImage = new RealImage(this.filename);
// 模拟延迟加载
setTimeout(() => this.realImage.render(), 1000);
}
// 先显示占位图
document.write('<img src="placeholder.jpg" />');
}
}
实现代理模式的注意事项
- 避免过度设计:不要为每个对象都添加代理,仅在必要时使用(如敏感资源、远程调用)。
- 保持接口一致性:代理与真实对象必须实现相同的接口,确保客户端无感知。
- 性能权衡:代理会增加一层调用开销,需在控制需求与性能之间找到平衡点。
结论
代理模式如同软件系统中的“智能中间件”,通过间接访问机制解决了权限控制、性能优化和资源管理等复杂问题。无论是远程服务调用、敏感数据访问,还是图像加载优化,代理模式都能以优雅的方式提升系统设计的灵活性与安全性。
掌握代理模式的关键在于理解“间接访问”的核心思想,并在实际开发中根据需求选择合适的代理类型。随着项目复杂度的提升,合理使用代理模式将帮助开发者构建出更健壮、可维护的代码架构。
(全文约 1800 字)