《Head First 设计模式》:代理模式

本贴最后更新于 1476 天前,其中的信息可能已经时异事殊

正文

一、定义

代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。

要点:

  • 代理模式为一个对象创建了代理对象,让代理对象控制对该对象的访问。被代理的对象可以是远程的对象、创建开销大的对象或者需要安全控制的对象。
  • 代理类型:远程代理、虚拟代理、保护代理等。
    • 远程代理:控制访问远程对象。
    • 虚拟代理:控制访问创建开销大的资源。
    • 保护代理:基于权限控制对资源的访问。

二、实现步骤

1、创建主题接口

/**
 * 主题接口
 */
public interface Subject {

    /**
     * 请求主题执行某种动作
     */
    void request();
}

2、创建真实主题(被代理类),并继承主题接口

真实主题是真正做事的主题对象。

/**
 * 真正做事的真实主题(被代理类)
 */
public class RealSubject implements Subject {

    @Override
    public void request() {
        System.out.println("RealSubject do something...");
    }
}

3、创建主题代理(代理类),并继承主题接口

主题代理持有真正做事的真实主题,并控制对真实主题的访问。

/**
 * 主题代理(代理类)
 */
public class SubjectProxy implements Subject {

    /**
     * 持有真实主题
     */
    RealSubject realSubject;
    
    @Override
    public void request() {
        System.out.println("SubjectProxy receives and controls request, and entrust request to RealSubject...");
        // 代理并控制对真实主题的访问,比如权限控制、访问资源控制等
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        // ...
        
        // 将请求委托给真实主题
        realSubject.request();
    }
}

4、使用主题代理访问主题

public class Test {

    public static void main(String[] args) {
        // 代理
        SubjectProxy proxy = new SubjectProxy();
        // 通过代理请求主题
        proxy.request();
    }
}

三、举个栗子

1、背景

我们打算建立一个应用程序,用来展示你最喜欢的 CD 封面。CD 封面的图我们可以从一些网站的在线服务中获取。

唯一的问题是,限于连接带宽和网络负载,下载可能需要一些时间,所以在等待图像加载时,应该显示一些东西。我们也不希望在等待图像时,整个应用程序被挂起。一旦图像被加载完成,刚才显示的东西应该消失,然后图像显示出来。

2、实现

使用虚拟代理管理图片的加载、显示。

(1)创建图片接口

/**
 * 图片接口
 */
public interface Image {

    /**
     * 绘图
     */
    void paint();
}

(2)创建真正做事的图片类

/**
 * 真正进行图片操作的真实图片类
 */
public class RealImage implements Image {

    String url;
    String description;
    
    public RealImage(String url,  String description) {
        this.url = url;
        this.description = description;
        load(url);
    }
    
    @Override
    public void paint() {
        System.out.println("Image is painted");
    }

    /**
     * 模拟加载图片
     */
    private void load(String url) {
        try {
            System.out.println("Loading image from: " + url);
            for (int i = 0; i < 10; i++) {
                System.out.print("===");
                Thread.sleep(200);
            }
            System.out.println("===>100%");
            System.out.println("Loading image is finished!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

(3)创建图片代理

/**
 * 图片代理
 */
public class ImageProxy implements Image {
    
    RealImage realImage;
    String imageUrl;
    /**
     * 是否已加载图片
     */
    boolean hasLoaded = false;
    
    public ImageProxy(String imageUrl) {
        this.imageUrl = imageUrl;
    }

    @Override
    public void paint() {
        if (realImage != null) {
            // 有图片,则直接绘图
            realImage.paint();
        } else {
            // 没有图片,则先显示加载中的信息,再去加载并绘制图片
            System.out.println("Loading CD cover, please wait...");
            loadAndPaint();
        }
    }
    
    /**
     * 加载并绘制图片
     */
    private void loadAndPaint() {
        if (hasLoaded) {
            return;
        }
        hasLoaded = true;
        // 加载图片是一个比较耗时的操作,为了避免程序阻塞,采用异步处理
        new Thread(new Runnable() {
            @Override
            public void run() {
                realImage = new RealImage(imageUrl, "CD Cover");
                // 加载完后,再进行绘图
                realImage.paint();
            }
        }).start();
    }
}

(4)使用图片代理进行绘图

public class Test {

    public static void main(String[] args) throws InterruptedException {
        // 图片代理
        ImageProxy imageProxy = new ImageProxy("https://www.jingqueyimu.com/images/xxx.jpg");
        // 使用图片代理进行绘图
        System.out.println("首次绘图:");
        imageProxy.paint();
        Thread.sleep(3000);
        System.out.println("再次绘图:");
        imageProxy.paint();
    }
}

四、Java 动态代理

1、创建主题接口

/**
 * 主题接口
 */
public interface Subject {

    /**
     * 请求主题执行某种动作
     */
    void request();
}

2、创建真实主题(被代理类),并继承主题接口

/**
 * 真正做事的真实主题
 */
public class RealSubject implements Subject {

    @Override
    public void request() {
        System.out.println("RealSubject do something...");
    }
}

3、使用 Java 动态代理,代理真实主题

public class Test {

    public static void main(String[] args) {
        // 真实主题(被代理类)
        Subject realSubject = new RealSubject();
        
        // 调用处理器
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 代理实例的方法被调用时,最终会执行该方法
                System.out.println("InvocationHandler start to invoke...");
                // do something...
                // 调用真实主题(被代理类)的方法
                method.invoke(realSubject, args);
                // do something...
                return null;
            }
        };
        
        // 使用 Proxy 创建代理
        Subject subjectProxy = (Subject) Proxy.newProxyInstance(
                realSubject.getClass().getClassLoader(), 
                realSubject.getClass().getInterfaces(), 
                handler);
        
        // 通过代理发起请求
        subjectProxy.request();
    }
}
  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    198 引用 • 120 回帖
1 操作
jingqueyimu 在 2020-09-28 23:30:04 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...