23种设计模式-结构型模式-代理

简介

代理是一种结构型设计模式,让你能够提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许在把请求提交给对象前后进行一些处理。

问题

为什么要控制对于某个对象的访问呢?举个例子:有一个消耗大量系统资源的巨型对象,你只是偶尔需要使用它。
在这里插入图片描述

如上图,数据库连接的初始化和查询可能会非常缓慢,我们可以先实现延迟初始化,只在实际有需要时再创建这个对象。这样对象的所有客户端都要执行延迟初始代码。这很可能会带来很多重复代码。在理想情况下,我们希望把延迟初始化的代码直接放入这个对象的类里,但这并不一定能实现, 因为这个类可能是第三方封闭库的一部分。

解决方案

代理模式建议 新建一个和原来的服务对象接口相同的代理类,然后把代理对象传递给所有原始对象客户端。代理类接收到客户端请求后会创建实际的服务对象,并把所有工作委派给它。

在这里插入图片描述
如上图,代理把自己伪装成数据库对象,可以在客户端或实际数据库对象不知情的情况下处理延迟初始化和缓存查询结果的工作。
这有什么好处呢?如果需要在类的主要业务逻辑前后执行一些工作,你不需要修改原始类就能完成这个工作。而且由于代理实现的接口与原类相同,因此你可以在任何一个使用实际服务对象的客户端中使用它。

代码

// 服务接口
interface Database {
    
    
    String executeQuery(String sql);
}

// 真实服务对象
class RealDatabase implements Database {
    
    
    public RealDatabase() {
    
    
        initializeConnection(); // 耗时的连接初始化
    }

    private void initializeConnection() {
    
    
        System.out.println("正在建立数据库连接...");
    }

    @Override
    public String executeQuery(String sql) {
    
    
        System.out.println("执行真实查询: " + sql);
        return "结果数据"; // 示例返回值
    }
}

// 代理类(延迟加载和缓存)
class DatabaseProxy implements Database {
    
    
    private RealDatabase realDatabase; 
    private Map<String, String> cache = new HashMap<>(); // 查询结果缓存

    @Override
    public String executeQuery(String sql) {
    
    
        // 延迟初始化(虚拟代理模式)
        if (realDatabase == null) {
    
    
            realDatabase = new RealDatabase(); 
        }

        // 结果缓存逻辑(缓存代理)
        if (cache.containsKey(sql)) {
    
    
            System.out.println("[Proxy] 返回缓存结果: " + sql);
            return cache.get(sql);
        }

        String result = realDatabase.executeQuery(sql);
        cache.put(sql, result);
        return result;
    }
}

// 客户端交互
class Application {
    
    
    public static void main(String[] args) {
    
    
        Database proxy = new DatabaseProxy();

        // 第一次查询触发真实连接
        System.out.println(proxy.executeQuery("SELECT * FROM users"));
      
        // 重复查询使用缓存
        System.out.println(proxy.executeQuery("SELECT * FROM users"));
      
        // 新查询继续代理
        System.out.println(proxy.executeQuery("SELECT COUNT(*) FROM products"));
    }
}

核心设计要点

  1. 代理类与现实服务实现相同接口
  2. 第一次查询延迟初始化真实连接(虚拟代理模式)
  3. 哈希表缓存重复查询结果(缓存优化)
  4. 客户端代码不需感知代理存在(透明访问)

总结

在这里插入图片描述

  1. 服务接口(Ser­vice Inter­face):声明接口,代理必须遵循这个接口才能伪装成服务对象。
  2. 服务(Ser­vice)类提供了一些实用的业务逻辑。
  3. 代理(Proxy)类包含一个指向服务对象的引用成员变量。代理完成他的任务之后,(比如延迟初始化、记录日志、访问控制和缓存等),会把请求传递给服务对象。通常情况下,代理会对这个服务对象的整个生命周期进行管理。
  4. 客户端(Client)能通过同一接口跟服务或代理进行交互,所以你可在一切需要服务对象的代码中使用代理。