《Java Design Patterns》第十三章 代理模式

第十三章 代理模式

代理模式是常用的 对象结构型设计模式 之一,当无法直接访问某个对象时,可以通过一个代理对象来间接访问。

为了保证客户端使用的透明性,所访问的真实对象与代理对象需要实现相同的接口。

代理模式根据使用目的,又可以分为:保护代理、远程代理、虚拟代理、缓冲代理等。

一、代理模式概述

日常生活中,我们通过网站购买商品,商品对应的就是“真实主题角色”,而商品网站则是“代理主题角色”。

1.1 定义

代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。

代理模式通过引入一个新的代理对象,在客户端对象和目标对象之间起到中介的作用,去掉客户不能看到的内容和服务,或者增添客户需要的额外的新服务。

1.2 代理模式的结构

代理模式结构

代理模式的重点在于,1. 客户端针对 抽象角色 编程 2. 抽象对象实例化代理对象 3. 通过代理对象维护的真实角色对象,来调用真实业务,并通过代理对象实现的方法,来扩展真实业务员

1.3 代理模式中的几个角色

  1. Subject - 抽象主题角色
    1. 声明了真实主题和代理主题的共同接口
    2. 客户端通常只需要针对抽象主题角色进行编程
  2. Proxy - 代理主题角色
    1. 包含对真实主题的应用,可以在任何时候操作真实主题
    2. 代理主题角色提供与真实主题角色相同的接口,以便在任何时候替代真实主题
    3. 代理主题角色还能控制对真实主题的使用,负责在需要时创建、删除真实主题对象,对真实主题对象的使用加以约束
  3. RealSubject - 真实主题角色
    1. 定义了真实的业务操作
    2. 客户端可以通过代理角色,间接调用真实角色的操作

1.4 代理模式的核心 - 代理主题角色 Proxy

class Proxy implements Subject {
    // 维护一个对真实主题对象的引用
    private RealSubject subject = new RealSubject(); 

    public void preRequest(){
        // ...
    }

    /**
     * 实现Subject声明的抽象方法,调用真实主题角色的操作,并进行扩展
     */
    @Override
    public void request(){
        preRequest();
        subject.request();
        postRequest();
    }

    public void postRequest(){
        // ...
    }
}

二、几种常用的代理模式

  1. 远程代理 - Remote Proxy
    1. 为一个位于不同的地址空间的对象提供一个本地的代理对象
    2. 远程代理又称为 大使 - Ambassador
    3. 客户端无须关心实现具体业务的是谁,只需要按照服务接口定义的方式直接与本地主机中的代理对象交互即可
    4. DCOM、Web Service 都应用了远程代理模式
  2. 虚拟代理 - Virtual Proxy
    1. 如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建
      1. 通常可以结合多线程技术,一个线程用于显示代理对象,其他线程用于加载真实对象
  3. 保护代理 - Protect Proxy
    1. 控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限
  4. 缓冲代理 - Cache Proxy
    1. 为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果
  5. 智能引用代理 - Smart Reference Proxy
    1. 当一个对象被引用时,提供一些额外的操作,例如记录被调用的次数等

三、完整实现

/**
 * 代理模式
 */
// 数据库验证类 - 业务类
class AccessValidator {
    public Boolean validate(String userId) {
        System.out.println("在数据库中验证用户:" + userId + ",是否是合法用户?");
        if (userId.contains("aa")) {
            System.out.println("验证通过!");
            return true;
        } else {
            System.out.println("验证失败!");
            return false;
        }
    }
}

// 日志记录类 - 业务类
class Logger {
    public void log(String userId) {
        System.out.println("记录用户 " + userId + " 登录日志");
    }
}

// 抽象查询类 - 抽象主题角色
interface Searcher {
    String doSearch(String userId, String keyword);
}

// 具体查询类 - 真实主题角色
class RealSearcher implements Searcher {
    @Override
    public String doSearch(String userId, String keyword) {
        System.out.println("用户 " + userId + " 使用关键词 " + keyword + " 进行查询");
        return "查询到的具体内容";
    }
}

// 代理查询类 - 代理主题角色,维持对业务类、真实主题角色对象的引用
class ProxySearcher implements Searcher {
    // 维持一个对真实主题的引用
    private RealSearcher searcher = new RealSearcher();

    // 业务类
    private AccessValidator validator = new AccessValidator();
    private Logger logger = new Logger();

    @Override
    public String doSearch(String userId, String keyword) {
        // 如果身份验证成功,则继续执行
        if (this.validate(userId)) {
            // 调用真实主题对象的查询方法
            final String result = searcher.doSearch(userId, keyword);
            // 记录查询日志
            this.logger(userId);
            // 返回查询结果
            return result;
        }
        return null;
    }

    public boolean validate(String userId) {
        return validator.validate(userId);
    }

    public void logger(String userId) {
        logger.log(userId);
    }
}

public class ProxyPattern {
    public static void main(String[] args) {
        final ProxySearcher searcher = new ProxySearcher();
        final String search = searcher.doSearch("aa张三", "男士内衣");
        System.out.println("查询结果:"+search);
    }
}

执行结果如下:

执行结果

四、总结

优点

代理模式各类型的共同优点:

  1. 能够协调调用者和被调用者,一定程序上降低了系统耦合
  2. 客户端可以针对 抽象主题角色 进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有良好的灵活性和可扩展性

不同代理模式的特点:

  1. 远程代理,为两个不同地址空间对象的访问提供了一种实现机制,可以将一些消耗资源较多的对象和操作 移至性能更好的计算机上,提高系统的整体运行效率
  2. 虚拟代理通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销
  3. 缓冲代理,为某一个操作 的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,优化系统性能,缩短执行时间
  4. 保护代理,可以控制一个对象的访问权限,为不同用户提供不同级别的使用权限

缺点

  1. 由于增加了代理对象,可能会导致请求的处理速度变慢,如保护代理
  2. 实现代理模式需要额外的工作,实现过程可能变得十分复杂,如远程代理

适用场景

  1. 当客户端对象需要访问远程主机中的对象时,可以使用远程代理
  2. 当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象时,可以使用虚拟代理
    1. 从而降低系统开销、缩短运行时间
  3. 为某一个需要频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问时,可以使用缓冲代理
  4. 需要控制对象的访问权限时,可以使用保护代理
  5. 需要为一个对象的引用提供一些额外操作时,可以使用智能引用代理
文章作者: koral
文章链接: http://luokaiii.github.io/2019/07/15/读书笔记/《JavaDesignPatterns》/15.代理模式/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自