设计模式学习04:代理模式

本文转载:https://blog.csdn.net/u012153129/article/details/81806602

作者:

 

什么是代理模式
   代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。
   这句话看起来比较抽象,不太好理解,举个现实的例子:外卖。在有饿了么之类的外卖出现前,我们如果点了外卖很多时候都是由店家自己来配送的。但是在有了饿了么等外卖平台后,商家就把送餐的业务委托给外卖平台,由外卖小哥来配送食品。外卖平台在这里充当一个“中介”的角色,代理模式就跟这个很相似。

代码实现

 

静态代理


   以送外卖为例子来举个例子,整个过程中的核心是配送,所以我们首先定义一个接口,接口里有一个配送方法

public interface FoodDelivered {
    /**配送*/
    void send();
}

 



   上段代码中有个send()方法,该方法是用来实现配送功能的,按照以前的方式,配送服务需要商家来自己来配送,所以我们定义一个商家类,该商家类实现FoodDelivered接口

public class Restaurant implements FoodDelivered {

    @Override
    public void send() {
        System.out.println("派送食物");
    }
}

 

   商家类实现FoodDelivered接口,并实现了send方法进行配送食品。再写一个测试类来测试一下上述代码

public static void main(String[] args) {
        Restaurant r = new Restaurant();
        r.send();
    }

 


   控制台打印(执行结果)

派送食物

派送食物

Process finished with exit code 0

 


   测试方法里创建了一个商家对象,并调用send方法来进行配送服务,可以看到控制台里打印了派送食品,这里表示商家进行了配送服务。现在我们有了外卖平台,商家就可以把派送服务委托给外卖小哥来完成,外卖小哥便成了代理者,因为我们的小哥也是进行配送服务的,所以我们再定义一个外卖小哥的类,并实现
FoodDelivered接口

public class Deliveryman implements FoodDelivered {

    private Restaurant restaurant;

    public Deliveryman(Restaurant restaurant) {
        this.restaurant = restaurant;
    }

    @Override
    public void send() {
        System.out.println("外卖小哥派送");
        this.restaurant.send();
    }
}

 

   这里我们注意一下,Deliveryman类里有个成员是Restaurant类型的,这个类型是商家的类型,就是我们的委托人(注:这里构造方法的参数是Restaurant类型,也就是说我们的委托人可以是Restaurant类及其子类,这样灵活性很差。这个参数可以换成FoodDelivered类型,这样只要实现了FoodDelivered接口的所有类都可以作为委托人,灵活性会提高)。同时构造方法里会传一个Restaurant对象过来,已完成对成员restaurant的初始化。然后在send方法里调用委托人的send方法,并在前边打印一句“外卖小哥派送”,然后编写测试代码

public static void main(String[] args) {
        Restaurant restaurant = new Restaurant();
        Deliveryman deliveryman = new Deliveryman(restaurant);
        deliveryman.send();
    }

 


   控制台打印(执行结果)

外卖小哥派送
派送食物

Process finished with exit code 0

 



   这里可以我们首先创建一个商家对象,然后创建一个外卖小哥对象,并把商家对象作为参数传递给外卖小哥对象,最后我们调用外卖小哥的send方法,而不是商家的send方法,这样就把配送的服务委托给了外卖小哥来完成。控制台打印结果可以看到这个配送是由外卖小哥来配送的。
   以上方式我们称之为静态代理,静态代理是在代码编译期前进行的,代码编译后就无法再改变了。引入代理模式好处,很多时候我们的业务可能会增加部分功能,比如事务控制,日志记录或者其他一些功能。如果在每次有需求增加的时候,我们都直接去修改当前业务的代码,这个并不符合开闭原则(对修改关闭,对扩展开放),当我们使用代理模式后,可以修改代理类的方法,原业务代码并不做任何修改。还是按之前的例子来说,比如这个时候客户需要一瓶饮料,这个时候我们不用修改商家的配送方法,只需要在外卖小哥的配送方法里实现买饮料的功能就可以了,这样就实现了在不修改配送方法的前提下对配送功能进行了扩充。对于我们在平常的开发中,比如有个业务需要增加日志功能,如果要去修改原业务代码,一是破坏开闭原则,二是增加了工作量,这个时候我们就可以用代理模式对业务做很好的扩充。与静态代理对应的还有一种是动态代理。

 

动态代理


   动态代理跟静态代理一个很重要的区别在于,动态代理是在内存是中的,是在代码编译期后在内存是实现的,而静态代理是我们自己编写代理类,编译后生成class文件。动态代理需要借助两个类:java.lang.reflect.InvocationHandler和java.lang.reflect.Proxy。我们还是以上边的例子来实现动态代理。首先需要创建一个类,并实现java.lang.reflect.InvocationHandler接口

public class FoodProxy<T> implements InvocationHandler {

    public T obj;

    public FoodProxy(T t) {
        obj = t;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("开始。。。");
        Object result = method.invoke(obj, args);
        System.out.println("结束。。。");
        return result;
    }
}

 

   其中有一个泛型成员变量obj,通过构造法方法接收一个变量来对成员obj初始化。重点看invoke方法,这里用到了反射相关的知识,第二个参数是Method的类型的变量,在方法里我们通过method.invoke()来执行这个方法,并且在方法执行前后各打印一行字符串。现在我们来编写一个测试类

public class FoodProxyMain {

    public static void main(String[] args) {
        Restaurant restaurant = new Restaurant();
        FoodProxy<FoodDelivered> proxy = new FoodProxy<>(restaurant);
        FoodDelivered r = (FoodDelivered) Proxy.newProxyInstance(FoodDelivered.class.getClassLoader(), new Class[]{FoodDelivered.class}, proxy);
        r.send();
    }

}

 

   首先创建一个要代理的对象(商家):restaurant,然后再创建一个InvocationHandler接口的实现类对象,也就是FoodProxy对象,因为我们要代理商家,所以把restaurant作为参数传递给FoodProxy的构造方法。接着通过Proxy.newProxyInstance()方法来创建我们的代理对象。我们可以看一下newProxyInstance的参数说明

  

 

 第一个参数是代理类的加载器,第二个参数是代理类要实现的接口列表,第三个就是要绑定InvocationHandler对象。然后把执行返回的对象强转为FoodDelivered类型的对象,并调用send()方法,看控制台打印

开始。。。
派送食物
结束。。。

Process finished with exit code 0

 

   可以看一下,控制台打印内容是InvocationHandler的实现类FoodProxy的invoke里的内容,我们在调用r.send()的时候,并不是直接去执行被代理类(Restaurant)的send()方法,而是委托给了FoodProxy的invoke去执行,也就是invoke方法里的

Object result = method.invoke(obj, args);

 


这行代码。这样就实现了对商家(Restaurant)的动态代理。这里我们可以修改一下测试类的代码,把Proxy.newProxyInstance的第一个参数和第二个参数换成实现类试试

public class FoodProxyMain {

    public static void main(String[] args) {
        Restaurant restaurant = new Restaurant();
        FoodProxy<FoodDelivered> proxy = new FoodProxy<>(restaurant);
        FoodDelivered r = (FoodDelivered) Proxy.newProxyInstance(Restaurant.class.getClassLoader(), new Class[]{Restaurant.class}, proxy);
        r.send();
    }

}

 

   注意:这里参数都改为了实现类的类型,然后我们运行一下代码

Exception in thread "main" java.lang.IllegalArgumentException: com.memory.proxy.Restaurant is not an interface
    at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:590)
    at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:557)
    at java.lang.reflect.WeakCache$Factory.get(WeakCache.java:230)
    at java.lang.reflect.WeakCache.get(WeakCache.java:127)
    at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:419)
    at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:719)
    at com.memory.proxy.FoodProxyMain.main(FoodProxyMain.java:14)

 



 

  程序出现了异常,异常信息:com.memory.proxy.Restaurant is not an interface。提示Restaurant不是一个接口。这里就说明,我们要动态代理的必须是个接口,如果你要动态实现的类没有实现任何接口,那很抱歉,你将无法进行动态代理。因为我们的Restaurant类已经实现了FoodDelivered接口,所以我们在这里参数使用FoodDelivered接口就不会有问题。

总结


   代理模式是JAVA设计模式里非常重要,也是经常使用的设计模式,代理模式可以在不改变原程序代码的前提下,对功能进行扩充。通过代理模式,我们可以很方便的给代码添加日志记录,事务控制,权限控制等一系列功能。Spring AOP(面向切面编程)就用到了动态代理模式,在往后也会对Spring AOP进行研究和分析。

 

 

 

 

posted on 2019-05-08 20:04  我不吃番茄  阅读(185)  评论(0编辑  收藏  举报