设计模式之代理模式

《Head First 设计模式》阅读笔记

Posted by wangtiegang on April 25, 2020

代理模式在平时工作中非常有用,各种框架都使用了代理模式,本书对代理模式的定义为

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

代理跟被代理的对象具有一样的方法和接口,只是代理不做具体功能的实现,被调用时会调用被代理对象的方法去做具体操作。代理模式有各种各样的变体,这些变体几乎都和“控制访问”的做法有关,以下为几种不同角度不同作用的举例

正向代理和反向代理

这个概率在书上是没有介绍的,但是在工作中经常碰到,简单的理解就是,在客户端请求服务端的过程中,假设客户端创建了代理,通过代理访问服务,那么就是正向代理,服务端不知道中间存在代理,比如使用vpn翻墙。反向代理就是服务端创建了代理,只告诉客户端代理的访问方式,这样服务端就隐藏起来了,客户端不知道自己访问的是代理,比如配置nginx反向代理。

远程代理

书本着重介绍了远程代理,使用 rmi 调用远程服务器上的方法,rmi 现在用得很少了,就不过多介绍。做的事情就感觉跟 webservice 差不多,都是请求远程的服务,就跟调用本地服务一样。

虚拟代理

虚拟代理作为创建开销大的对象的代表,在真正需要一个对象的时候才会去创建该对象,这样在创建前由代理来作为一个替身,一旦对象创建之后,代理就会将请求直接委托给对象。

书上举了一个请求cd封面的例子,由于下载cd封面开销大耗时长,可以通过虚拟代理在下载的过程中给用户一个比较好的体验,直接看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/**
 * Icon 接口,获取宽和高,绘制 icon 到面板
 */
public interface Icon {

    int getIconWidth();

    int getIconHeight();

    void paintIcon(Component c, Graphics g, int x, int y);

}

/**
* Icon 对象
*/
public class ImageIcon implements Icon {

    private Image image;

    /**
     * 在构造方法中通过 url 去下载图片,假设这个非常耗时
     * @param url
     */
    public ImageIcon(String url){
        // 省略下载代码
        // this.image = requestDownload(url);
    }

    @Override
    public int getIconWidth() {
        return image.getWidth(null); // 伪代码
    }

    @Override
    public int getIconHeight() {
        return image.getHeight(null); // 伪代码
    }

    @Override
    public void paintIcon(Component c, Graphics g, int x, int y) {
        // 绘制获取到到 icon
    }

}

/**
 * 虚拟代理,跟对象实现同一个接口
 */
public class ImageProxy implements Icon {
    ImageIcon imageIcon;

    String url;

    Thread retrievalThread;

    /**
     * 如果imageIcon已创建完成,则返回icon的宽,否则返回虚拟的800
     * @return
     */
    @Override
    public int getIconWidth() {
        if(imageIcon != null){
            return imageIcon.getIconWidth();
        }
        return 800;
    }

    @Override
    public int getIconHeight() {
        if(imageIcon != null){
            return imageIcon.getIconHeight();
        }
        return 800;
    }

    /**
     * 如果imageIcon已创建完成,则绘制到 g,否则绘制一个加载中,并创建一个真的icon对象,开始下载
     * @param c
     * @param g
     * @param x
     * @param y
     */
    @Override
    public void paintIcon(Component c, Graphics g, int x, int y) {
        if(imageIcon != null){
            imageIcon.paintIcon(c, g, x, y);
        }else{
            // 先绘制一个加载中
            g.drawString("加载中...", x, y);
            // 创建一个线程,通过线程创建真正到icon对象
            retrievalThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    // 在线程中完成耗时的下载过程
                    imageIcon = new ImageIcon(url);
                    // 下载完成之后触发组件重新绘制,代理将请求直接委托给对象
                    c.repaint();
                }
            });
            retrievalThread.start();
        }
    }

}

静态代理

静态代理就是程序员手动创建的代理对象,比如虚拟代理就是一种静态代理,这种代理的特点是有多少对象,就得手写多少代理。假设有个需求,需要在项目中所有的sql执行方法前打印执行的sql语句,那么就得为每一个 dao 层的对象创建一个代理。当然实际工作中这样做的话就累死了。

动态代理

动态代理就是由程序自动生成代理,从而不需要一个个创建,避免重复工作,书上没有涉及这一块,这个展开就太多了。