什么是代理模式?
代理模式很像我们生活中的"中间人"。想象一下,当你请明星参加活动时,你通常不会直接联系明星本人,而是联系他的经纪人。这个经纪人就是一个"代理",他代表明星处理各种事务,筛选请求,安排日程等。
在编程中,代理模式也是这样工作的:我们创建一个"代理对象",它控制着对另一个"真实对象"的访问。当你想使用真实对象时,你实际上是在和代理对象打交道。
为什么要用代理模式?
思考场景:
- 你想在访问某个重要文件前检查使用者的权限
- 你需要延迟加载一个很大的图片,直到真正需要显示它时
- 你希望记录每次对数据库的访问操作
- 你要在网络上远程调用另一台计算机上的程序
这些场景都很适合使用代理模式,它可以帮我们:
- 控制访问:像保安一样,检查谁能访问对象
- 延迟加载:懒惰一点,等真正需要时才创建开销大的对象
- 增加功能:在不改变原对象的情况下,添加新功能
- 远程访问:让远程对象看起来就像本地对象一样易用
代理模式长什么样?
用一个餐厅点餐的例子来说明。顾客(客户端)不直接告诉厨师(真实对象)做什么菜,而是告诉服务员(代理),服务员再把请求传给厨师。
// 菜单接口
interface FoodService {
void orderFood(String food);
}
// 厨师(真实对象)
class Chef implements FoodService {
@Override
public void orderFood(String food) {
System.out.println("厨师正在烹饪: " + food);
}
}
// 服务员(代理对象)
class Waiter implements FoodService {
private Chef chef = new Chef();
@Override
public void orderFood(String food) {
System.out.println("服务员记录订单: " + food);
// 检查库存
if ("鱼翅".equals(food)) {
System.out.println("抱歉,鱼翅已售罄");
return;
}
// 转发给厨师
chef.orderFood(food);
System.out.println("服务员准备上菜: " + food);
}
}
// 顾客使用
class Customer {
public static void main(String[] args) {
FoodService waiter = new Waiter();
waiter.orderFood("宫保鸡丁");
waiter.orderFood("鱼翅");
}
}
在这个例子中,Waiter
(服务员)就是一个代理,它实现了和Chef
(厨师)相同的接口,但在转发请求前后添加了额外的处理。
代理模式有哪些种类?
1. 静态代理:写死在代码里的代理
上面餐厅的例子就是静态代理。代理关系在写代码时就确定了,服务员只能代理厨师。
生活例子:固定的售票窗口只能卖特定线路的车票。
2. 动态代理:灵活多变的代理
不用事先为每个对象写好代理类,而是在程序运行时动态创建代理。
生活例子:万能中介,今天代理房屋出租,明天代理二手车买卖。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 动态代理处理器
class DynamicProxyHandler implements InvocationHandler {
private Object target; // 可以是任何对象
public DynamicProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始处理: " + method.getName());
// 调用目标对象的方法
Object result = method.invoke(target, args);
System.out.println("处理完成: " + method.getName());
return result;
}
}
// 动态代理示例
public class DynamicProxyDemo {
public static void main(String[] args) {
// 创建真实对象
Chef chef = new Chef();
// 创建动态代理
FoodService proxy = (FoodService) Proxy.newProxyInstance(
FoodService.class.getClassLoader(),
new Class<?>[] { FoodService.class },
new DynamicProxyHandler(chef)
);
// 使用代理
proxy.orderFood("糖醋排骨");
}
}
3. 远程代理:远在天边,近在眼前
让远程对象看起来像本地对象一样。
生活例子:你在国内用支付宝付款,但实际上商品在美国,支付宝作为代理处理了跨国支付细节。
4. 保护代理:守门员
控制对敏感对象的访问。
生活例子:保安检查你的门禁卡,确认你有权限才让你进入大楼。
5. 虚拟代理:按需加载
延迟创建开销大的对象,直到真正需要。
生活例子:你打开网页时,图片可能显示一个占位符,然后慢慢加载出来。
class HeavyImage implements Image {
private String filename;
public HeavyImage(String filename) {
this.filename = filename;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("加载大图片: " + filename + "(这很慢)");
// 模拟耗时操作
try { Thread.sleep(1000); } catch (InterruptedException e) { }
}
public void display() {
System.out.println("显示: " + filename);
}
}
class ImageProxy implements Image {
private String filename;
private HeavyImage realImage;
public ImageProxy(String filename) {
this.filename = filename;
}
public void display() {
// 延迟加载,直到真正需要显示时
if (realImage == null) {
realImage = new HeavyImage(filename);
}
realImage.display();
}
}
实际生活中的代理模式
明星和经纪人
- 明星:真实对象
- 经纪人:代理
- 粉丝/商家:客户端
经纪人负责筛选邀约、谈判报酬、安排日程,保护明星不被骚扰。
网购和电商平台
- 生产厂家:真实对象
- 电商平台:代理
- 消费者:客户端
淘宝、京东等平台作为代理,负责展示商品、处理支付、物流等。
法律代理
- 法律:真实对象
- 律师:代理
- 当事人:客户端
律师代表当事人处理法律事务,提供专业建议和服务。
实用场景示例:图片加载器
假设我们在开发一个相册应用,需要显示大量高清图片。如果一次性加载所有图片,会很慢且浪费内存。我们可以使用代理模式来延迟加载图片:
interface Image {
void display();
}
// 真实图片(重量级对象)
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("加载图片:" + filename);
// 模拟耗时的加载过程
try { Thread.sleep(1000); } catch (InterruptedException e) { }
}
@Override
public void display() {
System.out.println("显示图片:" + filename);
}
}
// 图片代理
class ProxyImage implements Image {
private String filename;
private RealImage realImage;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
// 相册应用
public class PhotoAlbum {
public static void main(String[] args) {
// 创建相册(使用代理)
Image[] album = new Image[5];
album[0] = new ProxyImage("高清风景1.jpg");
album[1] = new ProxyImage("高清风景2.jpg");
album[2] = new ProxyImage("高清风景3.jpg");
album[3] = new ProxyImage("高清风景4.jpg");
album[4] = new ProxyImage("高清风景5.jpg");
// 浏览相册(只有查看的图片才会被加载)
System.out.println("打开相册...");
// 用户只查看了第3张图片
album[2].display();
}
}
实用场景示例:权限控制
假设我们有一个文件系统,不同用户有不同的访问权限:
// 文件接口
interface File {
String read();
void write(String content);
}
// 真实文件
class RealFile implements File {
private String name;
private String content;
public RealFile(String name) {
this.name = name;
this.content = "这是" + name + "的内容";
}
@Override
public String read() {
System.out.println("读取文件: " + name);
return content;
}
@Override
public void write(String content) {
System.out.println("写入文件: " + name);
this.content = content;
}
}
// 文件访问代理
class FileAccessProxy implements File {
private RealFile realFile;
private String user;
public FileAccessProxy(String filename, String user) {
this.realFile = new RealFile(filename);
this.user = user;
}
@Override
public String read() {
// 所有用户都可以读取
return realFile.read();
}
@Override
public void write(String content) {
// 检查写入权限
if ("admin".equals(user)) {
realFile.write(content);
} else {
System.out.println("权限不足:用户 " + user + " 没有写入权限");
}
}
}
// 文件系统
public class FileSystem {
public static void main(String[] args) {
// 普通用户访问
File userFile = new FileAccessProxy("用户数据.txt", "张三");
System.out.println(userFile.read()); // 允许读取
userFile.write("尝试修改内容"); // 拒绝写入
System.out.println();
// 管理员访问
File adminFile = new FileAccessProxy("用户数据.txt", "admin");
System.out.println(adminFile.read()); // 允许读取
adminFile.write("管理员修改的内容"); // 允许写入
}
}
代理模式的优点
- 单一职责:真实对象专注核心功能,代理对象负责控制逻辑
- 扩展性好:不改变原对象,就能在外部添加功能
- 保护真实对象:限制对真实对象的直接访问
- 降低系统耦合度:客户端和真实对象不直接交互
代理模式的缺点
- 增加复杂度:新增了代理类,系统更复杂
- 可能降低性能:因为多了一层调用,特别是动态代理
- 可能引入不透明性:客户端不知道是在和代理打交道还是真实对象
代理模式和其他模式的区别
代理模式 vs 装饰器模式
- 代理模式:控制对象访问
- 装饰器模式:扩展对象功能
区别在于意图:门卫(代理)决定是否让你进入,而化妆师(装饰器)则是让你看起来更漂亮。
代理模式 vs 适配器模式
- 代理模式:接口相同,加入控制
- 适配器模式:转换不兼容的接口
区别在于:翻译(适配器)帮你和说不同语言的人交流,而律师(代理)则是以你的名义和法官交流。
什么时候用代理模式?
当你遇到这些情况时,考虑使用代理模式:
- 需要控制对某个对象的访问
- 需要在不修改已有代码的情况下添加功能
- 对象创建成本高,需要延迟初始化
- 需要在远程操作中隐藏复杂性
- 需要统一处理对多个对象的访问(如缓存、日志)
如何在实际项目中应用代理模式?
- 确定真实对象:明确需要被代理的对象
- 设计公共接口:代理和真实对象需要实现相同的接口
- 实现代理类:基于需求添加额外功能
- 选择代理类型:静态代理简单直接,动态代理更灵活
- 考虑性能影响:避免在性能关键路径上使用过于复杂的代理
总结
代理模式就像生活中的"中间人",它站在客户和真实对象之间,控制和扩展对真实对象的访问。无论是权限控制、延迟加载,还是增加额外功能,代理模式都能优雅地解决这些问题。
关键是理解代理模式的核心思想:通过引入一个新的对象来控制对原对象的访问。一旦掌握了这个思想,你就能在各种情景中灵活运用代理模式,让你的代码更加健壮和易于扩展。