结构型模式:代理模式
什么是代理模式?
代理模式很像我们生活中的"中间人"。想象一下,当你请明星参加活动时,你通常不会直接联系明星本人,而是联系他的经纪人。这个经纪人就是一个"代理",他代表明星处理各种事务,筛选请求,安排日程等。
在编程中,代理模式也是这样工作的:我们创建一个"代理对象",它控制着对另一个"真实对象"的访问。当你想使用真实对象时,你实际上是在和代理对象打交道。
为什么要用代理模式?
思考场景:
- 你想在访问某个重要文件前检查使用者的权限
- 你需要延迟加载一个很大的图片,直到真正需要显示它时
- 你希望记录每次对数据库的访问操作
- 你要在网络上远程调用另一台计算机上的程序
这些场景都很适合使用代理模式,它可以帮我们:
- 控制访问:像保安一样,检查谁能访问对象
- 延迟加载:懒惰一点,等真正需要时才创建开销大的对象
- 增加功能:在不改变原对象的情况下,添加新功能
- 远程访问:让远程对象看起来就像本地对象一样易用
代理模式长什么样?
用一个餐厅点餐的例子来说明。顾客(客户端)不直接告诉厨师(真实对象)做什么菜,而是告诉服务员(代理),服务员再把请求传给厨师。
// 菜单接口
interface FoodService {void orderFood(String food);
}// 厨师(真实对象)
class Chef implements FoodService {@Overridepublic void orderFood(String food) {System.out.println("厨师正在烹饪: " + food);}
}// 服务员(代理对象)
class Waiter implements FoodService {private Chef chef = new Chef();@Overridepublic 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;}@Overridepublic 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) { }}@Overridepublic void display() {System.out.println("显示图片:" + filename);}
}// 图片代理
class ProxyImage implements Image {private String filename;private RealImage realImage;public ProxyImage(String filename) {this.filename = filename;}@Overridepublic 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 + "的内容";}@Overridepublic String read() {System.out.println("读取文件: " + name);return content;}@Overridepublic 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;}@Overridepublic String read() {// 所有用户都可以读取return realFile.read();}@Overridepublic 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 适配器模式
- 代理模式:接口相同,加入控制
- 适配器模式:转换不兼容的接口
区别在于:翻译(适配器)帮你和说不同语言的人交流,而律师(代理)则是以你的名义和法官交流。
什么时候用代理模式?
当你遇到这些情况时,考虑使用代理模式:
- 需要控制对某个对象的访问
- 需要在不修改已有代码的情况下添加功能
- 对象创建成本高,需要延迟初始化
- 需要在远程操作中隐藏复杂性
- 需要统一处理对多个对象的访问(如缓存、日志)
如何在实际项目中应用代理模式?
- 确定真实对象:明确需要被代理的对象
- 设计公共接口:代理和真实对象需要实现相同的接口
- 实现代理类:基于需求添加额外功能
- 选择代理类型:静态代理简单直接,动态代理更灵活
- 考虑性能影响:避免在性能关键路径上使用过于复杂的代理
总结
代理模式就像生活中的"中间人",它站在客户和真实对象之间,控制和扩展对真实对象的访问。无论是权限控制、延迟加载,还是增加额外功能,代理模式都能优雅地解决这些问题。
关键是理解代理模式的核心思想:通过引入一个新的对象来控制对原对象的访问。一旦掌握了这个思想,你就能在各种情景中灵活运用代理模式,让你的代码更加健壮和易于扩展。