结构型模式:代理模式

什么是代理模式?

代理模式很像我们生活中的"中间人"。想象一下,当你请明星参加活动时,你通常不会直接联系明星本人,而是联系他的经纪人。这个经纪人就是一个"代理",他代表明星处理各种事务,筛选请求,安排日程等。

在编程中,代理模式也是这样工作的:我们创建一个"代理对象",它控制着对另一个"真实对象"的访问。当你想使用真实对象时,你实际上是在和代理对象打交道。

为什么要用代理模式?

思考场景:

  • 你想在访问某个重要文件前检查使用者的权限
  • 你需要延迟加载一个很大的图片,直到真正需要显示它时
  • 你希望记录每次对数据库的访问操作
  • 你要在网络上远程调用另一台计算机上的程序

这些场景都很适合使用代理模式,它可以帮我们:

  1. 控制访问:像保安一样,检查谁能访问对象
  2. 延迟加载:懒惰一点,等真正需要时才创建开销大的对象
  3. 增加功能:在不改变原对象的情况下,添加新功能
  4. 远程访问:让远程对象看起来就像本地对象一样易用

代理模式长什么样?

用一个餐厅点餐的例子来说明。顾客(客户端)不直接告诉厨师(真实对象)做什么菜,而是告诉服务员(代理),服务员再把请求传给厨师。

// 菜单接口
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("管理员修改的内容");  // 允许写入
    }
}

代理模式的优点

  1. 单一职责:真实对象专注核心功能,代理对象负责控制逻辑
  2. 扩展性好:不改变原对象,就能在外部添加功能
  3. 保护真实对象:限制对真实对象的直接访问
  4. 降低系统耦合度:客户端和真实对象不直接交互

代理模式的缺点

  1. 增加复杂度:新增了代理类,系统更复杂
  2. 可能降低性能:因为多了一层调用,特别是动态代理
  3. 可能引入不透明性:客户端不知道是在和代理打交道还是真实对象

代理模式和其他模式的区别

代理模式 vs 装饰器模式

  • 代理模式:控制对象访问
  • 装饰器模式:扩展对象功能

区别在于意图:门卫(代理)决定是否让你进入,而化妆师(装饰器)则是让你看起来更漂亮。

代理模式 vs 适配器模式

  • 代理模式:接口相同,加入控制
  • 适配器模式:转换不兼容的接口

区别在于:翻译(适配器)帮你和说不同语言的人交流,而律师(代理)则是以你的名义和法官交流。

什么时候用代理模式?

当你遇到这些情况时,考虑使用代理模式:

  1. 需要控制对某个对象的访问
  2. 需要在不修改已有代码的情况下添加功能
  3. 对象创建成本高,需要延迟初始化
  4. 需要在远程操作中隐藏复杂性
  5. 需要统一处理对多个对象的访问(如缓存、日志)

如何在实际项目中应用代理模式?

  1. 确定真实对象:明确需要被代理的对象
  2. 设计公共接口:代理和真实对象需要实现相同的接口
  3. 实现代理类:基于需求添加额外功能
  4. 选择代理类型:静态代理简单直接,动态代理更灵活
  5. 考虑性能影响:避免在性能关键路径上使用过于复杂的代理

总结

代理模式就像生活中的"中间人",它站在客户和真实对象之间,控制和扩展对真实对象的访问。无论是权限控制、延迟加载,还是增加额外功能,代理模式都能优雅地解决这些问题。

关键是理解代理模式的核心思想:通过引入一个新的对象来控制对原对象的访问。一旦掌握了这个思想,你就能在各种情景中灵活运用代理模式,让你的代码更加健壮和易于扩展。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Luck_ff0810

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值