一、代理模式介绍

1. 解决的问题

主要解决直接访问对象时带来的问题,比如对象创建开销过大或安全控制等。

2. 定义

代理模式是一种结构型设计模式,能够提供对象的替代品或其占位符。

代理控制着对于原对象的访问,并允许在将请求提交给对象前后进行一些处理。

3. 应用场景

  • 延迟初始化(虚拟代理):如果有一个偶尔使用的重量级服务对象,一直保持该对象运行会消耗系统资源时,可使用代理模式。
  • 访问控制(保护代理):如果只希望特定客户端使用服务对象(该对象可能非常重要),而客户端则是各种已启动的程序(包括恶意程序),可使用代理模式。
  • 本地执行远程服务(远程代理):服务对象位于远程服务器的场景。
  • 记录日志请求(日志记录代理):适用于需要保存对服务对象的请求历史记录时,代理可以在向服务传递请求前进行记录。
  • 智能引用:可在没有客户端使用某个重量级对象时立即销毁对象。

二、代理模式优缺点

1. 优点

  • 可以在客户端毫无察觉的情况下控制服务对象。
  • 客户端对服务对象的生命周期没有特殊要求时,可以对上生命周期进行管理。
  • 即使服务对象还未准备好或不存在,代理也可以正常工作。
  • 开闭原则:可以在不对服务或客户端作出修改的情况下创建新代理。

2. 缺点

  • 代码可能会变得复杂,因为需要创建许多类。
  • 服务享元可能会延迟。

三、代理模式应用实例:视频 App 代理缓存

1. 实例场景

我们经常会在一些视频 APP 上看一些自己喜欢的电视剧等。

如果我们多次观看一个视频,App 并不会反复下载该视频,而是会复用首次下载的视频文件。

今天就以视频 App 下载缓存为例,来介绍代理模式。

2. 代理模式实现

2.1 工程结构
proxy-pattern
└─ src
    ├─ main
    │    └─ java
    │    └─ org.design.pattern.proxy
    │       ├─ model
    │       │    └─ Video.java
    │       ├─ service
    │       │    ├─ VideoService.java
    │       │    └─ impl
    │       │        └─ VideoServiceImpl.java
    │       └─ VideoCacheProxy.java
    └─ test
        └─ java
            └─ org.design.pattern.proxy.test
                  └─ VideoProxyTest.java
2.2 代码实现
2.2.1 实体

视频实体类

/**
 * 视频实体类
 */
@AllArgsConstructor
public class Video {

    /**
     * 视频 id
     */
    private String id;

    /**
     * 视频标题
     */
    private String title;

    /**
     * 视频地址
     */
    private String url;
}
2.2.2 服务

视频服务接口

/**
 * 视频服务
 */
public interface VideoService {

    /**
     * 热门视频
     *
     * @return HashMap<String, Video
     */
    HashMap<String, Video> popularVideoList() throws InterruptedException;

    /**
     * 获取视频内容
     *
     * @param id 视频id
     * @return Video
     */
    Video getVideo(String id) throws InterruptedException;
}

视频服务实现类

/**
 * 视频服务实现类
 */
@Slf4j
public class VideoServiceImpl implements VideoService {

    /**
     * 热门视频
     *
     * @return HashMap<String, Video
     */
    @Override
    public HashMap<String, Video> popularVideoList() throws InterruptedException {
        log.info("开始获取热门视频");
        // Mock获取视频列表较慢
        Thread.sleep(4000);
        // Mock视频列表数据
        HashMap<String, Video> videoHashMap = new HashMap<>();
        videoHashMap.put("1", new Video("1", "钢之炼金术师", "http://xx.com/video/1/"));
        videoHashMap.put("2", new Video("2", "刺客五六七", "http://xx.com/video/2/"));
        videoHashMap.put("3", new Video("3", "大鱼海棠", "http://xx.com/video/3/"));
        videoHashMap.put("4", new Video("4", "千与千寻", "http://xx.com/video/4/"));
        log.info("热门视频获取完毕");
        return videoHashMap;
    }

    /**
     * 获取视频内容
     *
     * @param id 视频id
     * @return Video
     */
    @Override
    public Video getVideo(String id) throws InterruptedException {
        log.info("获取视频{}", id);
        // Mock获取视频列表较慢
        Thread.sleep(2000);
        Video video = new Video(id, "镶金玫瑰", "http://xx.com/video/5/");
        log.info("视频{}获取完毕", id);
        return video;
    }
}
2.2.3 代理

视频缓存代理类

/**
 * 视频缓存代理类
 */
@Slf4j
public class VideoCacheProxy implements VideoService {

    /**
     * 视频服务
     */
    private VideoService videoService;

    /**
     * 热门视频缓存列表
     */
    private HashMap<String, Video> cachePopularVideoList = new HashMap<>();

    /**
     * 视频缓存列表
     */
    private HashMap<String, Video> cacheVideoList = new HashMap<>();

    public VideoCacheProxy() {
        this.videoService = new VideoServiceImpl();
    }

    /**
     * 热门视频
     *
     * @return HashMap<String, Video
     */
    @Override
    public HashMap<String, Video> popularVideoList() throws InterruptedException {
        if (ObjectUtils.isEmpty(cachePopularVideoList)) {
            cachePopularVideoList = videoService.popularVideoList();
        } else {
            log.info("从缓存中获取热门视频");
        }
        return null;
    }

    /**
     * 获取视频内容
     *
     * @param id 视频id
     * @return Video
     */
    @Override
    public Video getVideo(String id) throws InterruptedException {
        Video video = cacheVideoList.get(id);
        if (ObjectUtils.isEmpty(video)) {
            video = videoService.getVideo(id);
            cacheVideoList.put(id, video);
        } else {
            log.info("从缓存中获取视频{}", id);
        }
        return video;
    }
}
2.3 测试验证
2.3.1 测试验证类
/**
 * 视频代理缓存测试类
 */
public class VideoProxyTest {

    @Test
    public void testVideoProxy() throws InterruptedException {
        VideoService videoService = new VideoCacheProxy();
        videoService.popularVideoList();
        videoService.getVideo("1");
        videoService.popularVideoList();
        videoService.getVideo("1");
    }
}
2.3.2 测试结果
21:34:46.632 [main] INFO  o.d.p.p.s.impl.VideoServiceImpl - 开始获取热门视频
21:34:50.640 [main] INFO  o.d.p.p.s.impl.VideoServiceImpl - 热门视频获取完毕
21:34:50.640 [main] INFO  o.d.p.p.s.impl.VideoServiceImpl - 获取视频1
21:34:52.653 [main] INFO  o.d.p.p.s.impl.VideoServiceImpl - 视频1获取完毕
21:34:52.653 [main] INFO  o.d.pattern.proxy.VideoCacheProxy - 从缓存中获取热门视频
21:34:52.653 [main] INFO  o.d.pattern.proxy.VideoCacheProxy - 从缓存中获取视频1

Process finished with exit code 0

四、代理模式结构

代理模式-模式结构图

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

设计模式并不难学,其本身就是多年经验提炼出的开发指导思想,关键在于多加练习,带着使用设计模式的思想去优化代码,就能构建出更合理的代码。

源码地址:https://github.com/yiyufxst/design-pattern-java

参考资料:
小博哥重学设计模式:https://github.com/fuzhengwei/itstack-demo-design
深入设计模式:https://refactoringguru.cn/design-patterns/catalog


易羽fxst
158 声望5 粉丝