代理模式 - Proxy Patterns

来源:互联网 发布:淘宝下架宝贝重新上架 编辑:程序博客网 时间:2024/06/04 17:47

本篇的父博文是:设计模式 - Design Patterns

一定要记得:

设计模式不是创造发明,它只是对某类问题的经验总结,并在此基础上给出的解决问题的最优思路。
它只是在教你代码怎么写,所以,也可能你自己没学过设计模式时就已经想到了某种设计模式那样的写法了


一、代理模式的意义

Design Patterns 一书中对代理模式的整体性描述是这样一句话:

Provide a surrogate or placeholder for another object to control access to it.
代理模式为一个对象提供一个代理对象,以控制对这个对象的访问。

其实,代理模式很简单、很有趣当然也很有用。往下看就很快明白啦~

二、应用场景

所以,为什么要用代理模式呢?或者说代理模式适用于哪些场景呢?


1. 远程代理人(remote proxy)

试想这样一个场景:你需要引用一个网络中的远程对象(对象在其它计算机JVM中)来完成功能的调用,你该怎么引用?

HelloService helloService = new 这该写啥啊?();

千万别说没这种需求呀,其实稍微上一点规模的系统都会有多个应用的互相调用的需求。那么,在Java 中这代码我们该如何写呢?
这里写图片描述

没错啦~ 最优的代码模板就是用代理模式 的思想去做啦~

思路

在本地项目中创建一个远程对象的代理对象 - 作为远程对象的代表,并在代理对象中封装对远程程序的访问/调用细节。此时,代码中不再直接访问远程对象,而是访问这个代理对象。如此,你就如同访问一个本地对象那样访问了远程对象。

下面用一个简单写的订单服务 - OrderService作为例子来实现下。

实现步骤

  1. 首先是一个接口:订单服务,提供了一个方法创建订单。

    /** * 订单服务。 */public interface OrderService {    /**     * 创建订单。     * @param orderId 订单ID     * @return     */    String createOrder(Integer orderId) throws Exception;}
  2. 其次呢,其它程序员在远程主机(127.0.0.1)上实现了此接口,并启动了对外服务

    public class OrderRemoteService implements OrderService {    @Override    public String createOrder(Integer orderId) throws Exception {        return "我是127.0.0.1,你创建订单成功!ID:" + orderId;    }}

    启动对外服务

    /** * 启动对外远程服务的main方法类。 */public class RomteMain {    @SuppressWarnings({ "resource" })    public static void main(String[] args) throws Exception {        // 实际实现类 - 敲黑板!敲黑板!敲黑板!这里是上面那个实际实现类        OrderService orderSevice = new OrderRemoteService();        // 下面代码:搞一个服务Socket等着client的Socket来连接,连上了就根据约定读取client调用的什么方法,然后转而调用实际实现类,并返回结果        ServerSocket server = new ServerSocket(9999);// 监听端口        while (true) {            final Socket socket = server.accept();            new Thread(){                @Override                public void run() {                    try {                        ObjectInputStream input = new ObjectInputStream(socket.getInputStream());                        // 读方法名                        String methodName = input.readUTF();                        // 读参数类型                        Class<?> paramsType = (Class<?>)input.readObject();                        // 读参数值                        Object params = (Object)input.readObject();                        ////// 使用反射调用相关方法                        Method method = orderSevice.getClass().getMethod(methodName, paramsType);                        Object result = method.invoke(orderSevice, params);                        ////// 将结果写回给socket                         ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());                        output.writeObject(result);                    } catch (Exception e) {                        e.printStackTrace();                    }                }            }.start();        }    }}
  3. 然后呢,在我们自己程序里写一个代理类,代理类就作为上面那个实现类在我们本地一个代表

    /** * 我是远程对象的代理人。 * <p>代理对象去找幕后主使来完成任务</p> */public class OrderServiceProxy implements OrderService {    @SuppressWarnings("resource")    @Override    public String createOrder(Integer orderId) throws Exception {        // 1. 连接远程服务        Socket socket = new Socket("127.0.0.1", 9999);        ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());        // 自定一个写出顺序,远程对象那里也要按这个顺序读        output.writeUTF("createOrder");// 写出方法名        output.writeObject(Integer.class);// 写出参数类型        output.writeObject(orderId);// 写出参数值        // 2. 读取返回        ObjectInputStream input = new ObjectInputStream(socket.getInputStream());        return (String)input.readObject();    }}
  4. 最后呢,我们程序中要用那个远程对象,我们没法直接引用到它,没关系我们用它的代理人

    public class LocalMain {    public static void main(String[] args) throws Exception {        // 客户端引用代理对象,而不是实际对象        OrderService orderService = new OrderServiceProxy();        // 直接调用服务就好        System.out.println(orderService.createOrder(9527));    }}

    以上。

总结

  1. 我们已经简单写了一个代理模式 - 远程代理 Java 程序。

    顺便说下,我们写这个也是个简单的RPC 框架哦~~
    不过,有一定经验的读者会发现,上面代码还是比较挫的,这在Java 中叫所谓的“静态代理”。如果我们要多写几个代理类,多写几个方法,上面代码扩展性就极差了。
    不过啊,上面只是简单尝试实现了“远程代理”,如果要做一个稍微完善点的RPC ,我们当然需要用到Java 中的“动态代理”。实际上,Java 中之所以有“动态代理”,就是为了解决上面说的那类问题的。不过呢,这里暂且不表啦,这个不是主线剧情。

  2. 还有一点,是不是还会有人脑海中会有这样一种思考?这不就是个接口的实现类吗,怎么能叫代理模式呢?

    首先,再看一遍设计模式的概念。
    接下来说:你写一个实现类,是你依赖Java 提供的一种实现Interface的代码形式而写出的代码;而你写一个代理类是参照设计模式的方案和思路写出的代码;它们是两个领域两种完全不同的概念啦。


2. 虚拟代理(virtual proxy)

思路

如果一个对象的创建成本非常高且并不是随时需要它,那么在程序启动时我们就不需要创建此对象,而是仅仅创建此对象的一个代理,让客户端引用这个代理就好了。当客户端实际需要使用这个对象的时候,我们再创建这个对象。

实现步骤

/** * 4. 某个客户程序。 */public class ClientMain {    /** 只给客户端持有一个视频服务的代理类,而不是真实服务类 */    private static VideoService videoService = new MP4VideoProxyService();    public static void main(String[] args) throws Exception {        // 客户端程序启动了        System.out.println("程序启动了--------");        System.out.println("程序运行很久很久--------");        Thread.sleep(1000l);        // 很久后才突然被触发,需要执行以下代码        System.out.println("N小时候触发了某个事件,我们需要获取视频了--------");        String result = videoService.getVideo("长城");        System.out.println(result);    }}/** * 1. 视频服务“接口”。 */interface VideoService {    String getVideo(String videoName);}/** * 2. 视频服务“真实服务类”。 * <p>初始化很消耗资源,很耗时</p> */class MP4VideoService implements VideoService {    public MP4VideoService() {        // 很耗时、耗资源的初始化操作        System.out.println("我是很耗资源的真实服务,我被创建了!");    }    @Override    public String getVideo(String videoName) {        return "找到视频:[" + videoName + "]并返回";    }}/** * 3. 视频服务一个“代理类”。 */class MP4VideoProxyService implements VideoService {    /** 真实服务类的引用,当真正需要时才给其赋值 */    private VideoService mp4VideoService;    @Override    public String getVideo(String videoName) {        // 代理类的getVideo方法        if (mp4VideoService == null) {            mp4VideoService = new MP4VideoService();        }        return mp4VideoService.getVideo(videoName);    }}

3. 保护代理(protection proxy)

思路:

使用一个代理对象来控制一个实际对象的访问权限。这种用处就先不写代码了,很好想到,就是保护一个对象,使不同的客户端有不同的访问权限。


4. 智能引用(smart reference)

思路:

智能引用取代了简单的指针引用,在对象被访问时能让我们做一些额外的操作。

实现步骤:

这个最大名鼎鼎的应用就是AOP 了吧。
正好,接下来我使用上面没有使用的,Java动态代理 的方式来实现一个AOP 代码。

  1. 首先,我们创建一个interfaceCalculateService ,用来表示计算服务,并定义加、减、乘和除四个方法

    public interface CalculateService {    int calculateAdd(int a, int b);    int calculateSubtract(int a, int b);    int calculateMultiply(int a, int b);    int calculateDivide(int a, int b);}
  2. 然后,我们用一个类来实现这个接口的方法,这个类就是作为一个真是实现类

    public class CalculateServiceImpl implements CalculateService {    public int calculateAdd(int a, int b) {        return a + b;    }    public int calculateSubtract(int a, int b) {        return a - b;    }    public int calculateMultiply(int a, int b) {        return a * b;    }    public int calculateDivide(int a, int b) {        return a / b;    }}
  3. 最后,我们用动态代理的API,来实现AOP ,我们统计下耗费的时间。

    public class MainAOP {    // 现在我们有一个服务CalculateService,它提供了基本的加减乘除。    // * 突然的,你多了个需求,想在执行加减乘除方法时,都计算一下它执行所耗费的时间。    // ** 咋办?去改CalculateServiceImpl吗?有时候你没有权限改这个/你不应该侵入人家的代码/其它调用这个服务的人可能并不需要计时    // ** 那代码怎么写能很好的解决这个问题呢?    // *** 灯~灯~灯~灯…… 所以你学了设计模式,你想到“可以用aop的思想 + JavaSE中提供的Proxy等类的功能”来写代码。开始!    /** 计算服务 */    private static CalculateService calculateService = getCalculateService();    /************************************ 模拟使用↓ **************************************/    public static void main(String[] args) {        // 牛!        calculateService.calculateAdd(1, 1);    }    /**     * 获得计算服务。<br>     * 你利用你的aop思想,将代码写成了下面这样。<br>     * 你不费吹灰之力,没有写代理类.java的情况下,把功能给完成了。还对原代码无侵入,还能随意判断,以根据不同的方法去决定是否要计算时间。牛!     * @return     */    private static CalculateService getCalculateService() {        return (CalculateService) Proxy.newProxyInstance(                MainAOP.class.getClassLoader(),                 new Class[] { CalculateService.class },                 new InvocationHandler() {                    @Override                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                        long startTime = System.currentTimeMillis();                        // 核心调用实现类                        Object result = method.invoke(new CalculateServiceImpl(), args);                        long endTime = System.currentTimeMillis();                        // 此次aop的目的:输出耗时                        System.out.println("耗时:" + (endTime - startTime));                        return result;                    }                });    }}

    需要解释的点,代码中的注释都写上啦。


以上,我们学习了:设计模式之 –> 结构模式之 –> 代理模式,我觉得很简单和有趣啊。

请持续关注我的博客哈,会继续推出设计模式系列。


转载注明出处:http://blog.csdn.net/u010297957/article/details/53885454

1 0
原创粉丝点击