Proxy

设计模式目录

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

Problem

为什么要控制对于某个对象的访问呢? 举个例子: 有这样一个消耗大量系统资源的巨型对象, 你只是偶尔需要使用它, 并非总是需要。

你可以实现延迟初始化: 在实际有需要时再创建该对象。 对象的所有客户端都要执行延迟初始代码。 不幸的是, 这很可能会带来很多重复代码。

在理想情况下, 我们希望将代码直接放入对象的类中, 但这并非总是能实现: 比如类可能是第三方封闭库的一部分。

Solve

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

这有什么好处呢? 如果需要在类的主要业务逻辑前后执行一些工作, 你无需修改类就能完成这项工作。 由于代理实现的接口与原类相同, 因此你可将其传递给任何一个使用实际服务对象的客户端。

Java 标准程序库中的一些代理模式的示例:

识别方法: 代理模式会将所有实际工作委派给一些其他对象。 除非代理是某个服务的子类,否则每个代理方法最后都应该引用一个服务对象。

代理模式结构

样例

对低效的第三方 YouTube 集成程序库进行缓存。

远程服务接口

package structural.proxy;

import java.util.HashMap;

public interface ThirdPartyYouTubeLib {

    HashMap<String,Video> popularVideos();

    Video getVideo(String videoId);
}

远程服务实现

package structural.proxy;

import java.util.HashMap;

public class ThirdPartyYouTubeClass implements ThirdPartyYouTubeLib {

    @Override
    public HashMap<String, Video> popularVideos() {
        connectToServer("http:youtube.com");
        return getRandomVideo();
    }

    @Override
    public Video getVideo(String videoId) {
        connectToServer("http:youtube.com" + videoId);
        return getSomeVideo(videoId);
    }

    private int random(int min, int max) {
        return min + (int) (Math.random() * ((max - min) + 1));
    }

    private void experienceNetworkLatency() {
        int randomLatency = random(5, 10);
        for (int i = 0; i < randomLatency; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void connectToServer(String server) {
        System.out.println("Connecting to" + server + "...");
        experienceNetworkLatency();
        System.out.println("Connected!");
    }

    private HashMap<String, Video> getRandomVideo() {
        System.out.println("Downloading populars...");
        experienceNetworkLatency();
        HashMap<String, Video> hashMap = new HashMap<>();
        hashMap.put("catzzzzzzzzz", new Video("catzzzzzzzzz", "casssss.avi"));
        hashMap.put("mkafksangasj", new Video("mkafksangasj", "Dog play with ball.mp4"));
        hashMap.put("dancesvideoo", new Video("asdfas3ffasd", "Dancing video.mpq"));
        hashMap.put("dlsdk5jfslaf", new Video("dlsdk5jfslaf", "Barcelona vs RealM.mov"));
        hashMap.put("3sdfgsd1j333", new Video("3sdfgsd1j333", "Programing lesson#1.avi"));
        System.out.println("Done!");
        return hashMap;
    }

    private Video getSomeVideo(String videoId) {
        System.out.println("Downloading video... ");

        experienceNetworkLatency();
        Video video = new Video(videoId, "Some video title");

        System.out.println("Done!");
        return video;
    }
}

视频文件

package structural.proxy;

/**
 * 视频文件
 */
public class Video {

    public String id;
    public String title;
    public String data;

    public Video(String id, String title) {
        this.id = id;
        this.title = title;
        this.data = "Random Video.";
    }
}

缓存代理

package structural.proxy;

import java.util.HashMap;

/**
 * 缓存代理
 */
public class YouTubeCacheProxy implements ThirdPartyYouTubeLib {

    private ThirdPartyYouTubeLib youTubeService;
    private HashMap<String, Video> cachePopular = new HashMap<>();
    private HashMap<String, Video> cacheAll = new HashMap<>();

    public YouTubeCacheProxy() {
        this.youTubeService = new ThirdPartyYouTubeClass();
    }

    @Override
    public HashMap<String, Video> popularVideos() {
        if (cachePopular.isEmpty()) {
            cachePopular = youTubeService.popularVideos();
        } else {
            System.out.println("Retrieved list from cache.");
        }
        return cachePopular;
    }

    @Override
    public Video getVideo(String videoId) {
        Video video = cacheAll.get(videoId);
        if (video == null) {
            video = youTubeService.getVideo(videoId);
            cacheAll.put(videoId, video);
        } else {
            System.out.println("Retrieved video '" + videoId + "' from cache.");
        }
        return video;
    }

    public void reset() {
        cachePopular.clear();
        cacheAll.clear();
    }
}

媒体下载应用

package structural.proxy;

import java.util.HashMap;

public class YouTubeDownloader {
    private ThirdPartyYouTubeLib api;

    public YouTubeDownloader(ThirdPartyYouTubeLib api) {
        this.api = api;
    }

    public void renderVideoPage(String videoId){
        Video video = api.getVideo(videoId);
        System.out.println("
-------------------------------");
        System.out.println("Video page (imagine fancy HTML)");
        System.out.println("ID: " + video.id);
        System.out.println("Title: " + video.title);
        System.out.println("Video: " + video.data);
        System.out.println("-------------------------------
");
    }

    public void renderPopularVideos(){
        HashMap<String, Video> list = api.popularVideos();
        System.out.println("
-------------------------------");
        System.out.println("Most popular videos on YouTube (imagine fancy HTML)");
        for (Video video : list.values()) {
            System.out.println("ID: " + video.id + " / Title: " + video.title);
        }
        System.out.println("-------------------------------
");
    }
}

测试

package structural.proxy;

public class Demo {
    public static void main(String[] args) {
        YouTubeDownloader naiveDownloader = new YouTubeDownloader(new ThirdPartyYouTubeClass());
        YouTubeDownloader smartDownloader = new YouTubeDownloader(new YouTubeCacheProxy());

        long naive = test(naiveDownloader);
        long smart = test(smartDownloader);
        System.out.println("Time saved by caching proxy: " + (naive - smart) + "ms");
    }

    private static long test(YouTubeDownloader downloader) {
        long startTime = System.currentTimeMillis();

        downloader.renderPopularVideos();
        downloader.renderVideoPage("catzzzzzzzzz");
        downloader.renderPopularVideos();
        downloader.renderVideoPage("dancesvideoo");
        // Users might visit the same page quite often.
        downloader.renderVideoPage("catzzzzzzzzz");
        downloader.renderVideoPage("someothervid");

        long estimatedTime = System.currentTimeMillis() - startTime;
        System.out.print("Time elapsed: " + estimatedTime + "ms
");
        return estimatedTime;
    }
}

适用场景

  • 延迟初始化 (虚拟代理)。 如果你有一个偶尔使用的重量级服务对象, 一直保持该对象运行会消耗系统资源时, 可使用代理模式。

    你无需在程序启动时就创建该对象, 可将对象的初始化延迟到真正有需要的时候。

  • 访问控制 (保护代理)。 如果你只希望特定客户端使用服务对象, 这里的对象可以是操作系统中非常重要的部分, 而客户端则是各种已启动的程序 (包括恶意程序), 此时可使用代理模式。

    代理可仅在客户端凭据满足要求时将请求传递给服务对象

  • 本地执行远程服务 (远程代理)。 适用于服务对象位于远程服务器上的情形。

    在这种情形中, 代理通过网络传递客户端请求, 负责处理所有与网络相关的复杂细节。

  • 记录日志请求 (日志记录代理)。 适用于当你需要保存对于服务对象的请求历史记录时。代理可以在向服务传递请求前进行记录。

    缓存请求结果 (缓存代理)。 适用于需要缓存客户请求结果并对缓存生命周期进行管理时, 特别是当返回结果的体积非常大时。

    • 代理可对重复请求所需的相同结果进行缓存, 还可使用请求参数作为索引缓存的键值。
  • 智能引用。 可在没有客户端使用某个重量级对象时立即销毁该对象。

    代理会将所有获取了指向服务对象或其结果的客户端记录在案。 代理会时不时地遍历各个客户端, 检查它们是否仍在运行。 如果相应的客户端列表为空, 代理就会销毁该服务对象, 释放底层系统资源。

    代理还可以记录客户端是否修改了服务对象。 其他客户端还可以复用未修改的对象。

实现方式

  1. 如果没有现成的服务接口, 你就需要创建一个接口来实现代理和服务对象的可交换性。 从服务类中抽取接口并非总是可行的, 因为你需要对服务的所有客户端进行修改, 让它们使用接口。 备选计划是将代理作为服务类的子类, 这样代理就能继承服务的所有接口了。
  2. 创建代理类, 其中必须包含一个存储指向服务的引用的成员变量。 通常情况下, 代理负责创建服务并对其整个生命周期进行管理。 在一些特殊情况下, 客户端会通过构造函数将服务传递给代理。
  3. 根据需求实现代理方法。 在大部分情况下, 代理在完成一些任务后应将工作委派给服务对象。
  4. 可以考虑新建一个构建方法来判断客户端可获取的是代理还是实际服务。 你可以在代理类中创建一个简单的静态方法, 也可以创建一个完整的工厂方法。
  5. 可以考虑为服务对象实现延迟初始化。

代理模式优点

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

代理模式缺点

  • 代码可能会变得复杂, 因为需要新建许多类。
  • 服务响应可能会延迟。
原文地址:https://www.cnblogs.com/huaranmeng/p/14299525.html