设计模式 - 装饰模式和代理模式

参考博客:https://www.cnblogs.com/doucheyard/p/5737366.html

https://www.cnblogs.com/zhengbin/p/5618564.html

参考以上博客作出的小总结

写完下面的内容之后的总结:

要区分装饰模式和代理模式, 不能只从代码结构上区分, 而更要从两个模式的 使用目的  和 使用方式 区分

比如装饰器模式的目的, 是为了增强代码 , 为了让装饰之前的类具有更多的功能. 在使用方式上, 装饰器可以一层一层叠加使用

而代理模式 是为了控制对被代理的对象的访问的同时能使用其功能,  使用方式上, 使用者是看不到被代理的对象的

一.  装饰模式

   装饰模式没有被装饰类的控制权, 装饰类只是将被装饰类的功能拓展, 两个类都实现同一接口, 也就是装饰类可以装饰实现了这一接口的任何其他类

装饰器模式的特点就是, 我们常常将被装饰类作为参数传递进装饰器中, 如IO流的各种装饰器类

这里我们举个栗子:

(1) 首先实现一个女孩接口, 她有一个特点就是漂亮

public interface Girl {
    void beauty();
}

(2)然后你的女朋友实现了这个接口, 而且你女票长得很漂亮

public class GirlFriend implements Girl {
    @Override
    public void beauty() {
        System.out.println("your girlfriend is beautiful");
    }
}

(3)然后我们有一个可以打扮女孩的化妆师, 这个化妆师也是个女孩

public class Dresser implements Girl {

    private Girl customer;  //每个化妆师都有一个顾客

    public Dresser(Girl customer){  //顾客进店了
        this.customer = customer;
    }

    @Override
    public void beauty() {
        System.out.println("Dress girl");
        customer.beauty();
    }

}

(4)然后我们就来看看化妆之前和化妆之后的区别吧

    @Test
    public void WrapperTest(){
        Girl girlfriend = new GirlFriend();
        girlfriend.beauty();                       //化妆之前的女朋友

        Girl girlFriend = new Dresser(girlfriend); //女朋友被化妆师打扮后
        girlFriend.beauty();
    }
your girlfriend is beautiful
---------------------------
Dress girl
your girlfriend is beautiful

(5)让多个化妆师层层叠进化妆

定义另一个新的化妆师

public class OtherDresser implements Girl {

    private Girl girl;

    public OtherDresser(Girl girl){
        this.girl = girl;
    }

    @Override
    public void beauty() {
        System.out.println("Dress your girl too");
        girl.beauty();
    }
}

(6)让这个化妆师再帮你女朋友画一次妆

 Girl girlfriend = new GirlFriend();
        girlfriend.beauty();

        //分割线
        System.out.println("---------------------------");

        Girl girlFriend = new Dresser(girlfriend);
        girlFriend.beauty();

        //分割线
        System.out.println("---------------------------");
        Girl girl = new OtherDresser(girlFriend);
        girl.beauty();

得到的代码如下:

your girlfriend is beautiful
---------------------------
Dress girl
your girlfriend is beautiful
---------------------------
Dress your girl too
Dress girl
your girlfriend is beautiful

看出来装饰器的好处了吧? 其实根据对IO流的使用知识, 我们可以像这样使用装饰器

    @Test
    public void WrapperTest(){
        Girl girlfriend = new Dresser(new OtherDresser(new GirlFriend()));
        girlfriend.beauty();
    }

得到的结果如下:

Dress girl
Dress your girl too
your girlfriend is beautiful

只是随便举个例子,  为了更好的理解

 装饰器模式要关注的点是

  • 装饰器模式的作用是"锦上添花", 不使用装饰器模式下依然可以对被装饰的类进行访问
  • 被装饰的类作为参数传递进 装饰器中,  同一个装饰器可以指定装饰 不同的 被装饰类(只要实现的是统一接口)
  • 装饰器没有被装饰类的控制权, 不是它自己决定装饰的哪个类
  • 多个装饰器可以叠加使用

二. 代理模式

代理模式有被代理类的控制权,代理类可以继承被代理类的接口,比如静态代理, 也可以不继承接口,比如动态代理

但是代理模式下, 限制了客户端对被代理类的访问, 也就是客户端不知道被代理的是哪一个, 

代理模式的特点就是, 我们常常在代理类中实现被代理类的实例,  而不是将被代理类的实例作为参数传递进 代理类中... 这就是为啥限制了对被代理类的访问

原谅我不知道咋说

     代理模式有两种: 静态代理动态代理

     动态代理又分为: 基于继承接口类的动态代理 和 基于子类的动态代理

1.静态代理模式:

(1)实现同样的 接口+实现类

public interface Girl {
    void beauty();
}

public class GirlFriend implements Girl {
    @Override
    public void beauty() {
        System.out.println("your girl is beautiful");
    }
}

(2)创造一个代理类, 这个代理人也是一个女孩子, 但是又与装饰器不同

public class GirlProxy implements Girl {

    Girl girl;

    public GirlProxy(){
        girl = new GirlFriend();  //不同点: 看到没, 女朋友自己跑到代理人手上了  你自己直接是见不到女朋友的
    }

    @Override
    public void beauty() {
        System.out.println("the proxy said:");
        girl.beauty();
    }
}

(3)来看看,怎么用代理模式

public class ProxyTest {

    @Test
    public void test1(){
        Girl girlfriend = new GirlProxy(); //你没办法直接见你女朋友了, 你只能通过代理人见到
        girlfriend.beauty();
    }
}
the proxy said:
your girl is beautiful

这样一看, 与装饰器的区别就来了

  1. 在GirlProxy中, 它的构造器与Dresser的构造器 的区别 在于 有参和无参  这就意味着主动权从使用者这, 到了代理人那
  2. Dresser可以装饰任何传递进来的女孩,  而GirlProxy只能代理自己指定的女孩
  3. 在客户端, 是我们将GirlFriend交到Dresser手上的,  而使用GirlProxy时, 我们无法直接见到GirlFriend
  4. 代理模式无法叠加使用

2.动态代理模式

      静态代理的受限于接口的实现,而动态代理是通过反射,动态地获取对象接口的类型, 从而获取相关特性进行代理, 动态代理能够为所有委托方进行代理.     

      动态代理模式的经典应用就是Spring中的AOP

2.1 基于继承接口类的动态代理

(1)首先我们来实现一个动态代理, 就叫GenericProxy吧

public class GenericProxy implements InvocationHandler {
    
    private Object proxyTarget; //声明一个代理目标
    
    public Object getProxyInstance(Object target){
        this.proxyTarget = target; //通过这个方法,将代理目标和代理关联
        //返回一个代理后的实例,这里的参数可以参考AOP文章中的说明
        return Proxy.newProxyInstance(proxyTarget.getClass().getClassLoader(),proxyTarget.getClass().getInterfaces(),this);
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy do something");
        Object methodObject = method.invoke(proxyTarget,args);
        System.out.println("proxy end something");
        return methodObject;
    }
}

(2)然后我们看看动态代理的使用吧

    @Test
    public void test1(){
        Girl girlfriend = new GirlFriend();
        GenericProxy gp = new GenericProxy();
        Girl girlFriend = (Girl) gp.getProxyInstance(girlfriend);
        girlFriend.beauty();
    }

做到这里忽然发现, 奇怪, 为什么和装饰器一样了呢, 为啥我可以见到我女朋友了呢?

我查询动态代理和静态代理的区别后, 看到了这个博客, 了解了区别: https://www.cnblogs.com/baizhanshi/p/6611164.html

动态代理是为了解决静态代理的缺点: 每一个代理类只能为一个接口服务,如果想要转为增强另一个接口, 则需要一个新的代理类, 这样程序开发中必然会产生许多代理类

比如你只想测试所有接口的实现类的方法的性能, 但是你却要为每个接口写一个性能测试的代理类

因此,上面的基于接口实现类的动态代理就帮助你解决了这一问题, 因为它可以为任何Object类进行代理

2.2 基于子类的动态代理

   在一般情况下, 我们想增强的类可能是没有实现任何接口的普通类, 在这种情况下, 要如何对其进行代理?

   以下取自博客:https://www.cnblogs.com/doucheyard/p/5737366.html

    假设目前有个项目,A公司写了一个Computer类已经整合到某一个jar包里面,这个类是个普通类没有任何抽象角色(没有实现任何接口)。但是B公司需要对Computer类内的方法进行加强、拓展。那B公司怎么办?目前只有两个方法:1、拿A公司代码反编译,然后修改A公司里面这个类的方法。 2、拿A公司代码反编译,让A公司Computer类进行抽象化,然后B公司使用基于接口的动态代理对方法进行拓展。很明显,这两个方法都是违背了面向对象设计的思想的 (①高内聚低耦合 ②开闭原则[OCP]:对拓展开放,对修改关闭)

    在这种情况下基于子类的动态代理产生了。基于子类的动态代理不是由JDK内附功能。需要引入第三方的开发包:CGlib,使用这个子类的代理需要引入cglib-nodep.jar

     CGLib在代码上的使用基本和基于接口的动态代理一样,使用方法极为相似,仅表现在CGLib代理时使用的是方法拦截器MethodInterceptor。

(1)创建一个没有实现任何接口的普通类

public class GirlFriend {
    
    public void beauty(){
        System.out.println("your girlfriend is beautiful");
    }
    public void love(){
System.out.println("your girlfriend love you");
}
}

(2)实现一个基于子类的动态代理:  记得要有cglib的包

public class GirlProxy implements MethodInterceptor {
    
    private Object proxyTarget;
    
    public Object getProxyInstance(Object target){
        this.proxyTarget = target;
        return Enhancer.create(target.getClass(),target.getClass().getInterfaces(),this);
    }
    
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

        Object methodResult = null;
        
        if(method.getName().equals("beauty")){
            System.out.println("proxy do something");
            methodResult = method.invoke(proxyTarget,args); //启动委托类方法的第一种
            System.out.println("proxy do something end");
        } else {
            System.out.println("proxy dont wanna do something");
          methodProxy.invokeSuper(proxy,args);  //启动委托类方法的第二种
        }
        
        return methodResult;
    }
}

在这个类里, 我们实现了和前面类似的方法, 根据委托的方法的不同, 进行不同的增强

而在启动委托类方法 的方式 上 又有两种不同

(3)看看测试结果:

    @Test
    public void test3(){
        GirlProxy gp = new GirlProxy();
        GirlFriend gf = (GirlFriend)gp.getProxyInstance( new GirlFriend());
        gf.beauty();
        gf.love();
    }
proxy do something
your girlfriend is beautiful
proxy do something end
proxy dont wanna do something
your girlfriend love you

可以看到, 根据使用的方法的不同, 代理人进行了不同的操作(其实就是根据反射得到的方法名作出不同操作)

   这个的目的是为了说明, 代理类会在你每次调用委托类的方法时,进行一次intercept(拦截), 然后你就可以根据不同的方法进行不同的增强

(4)再试试让这个代理来代理其他类:

public class Child {
    public void eat(){
        System.out.println("child eat");
    }

    public void run(){
        System.out.println("child run");
    }

    public void breath(){
        System.out.println("child breath");
    }
}
    @Test
    public void test3(){
        GirlProxy gp = new GirlProxy();
        Child child = (Child) gp.getProxyInstance( new Child());
        child.breath();
        child.eat();
        child.run();
    }
proxy dont wanna do something
child breath
proxy dont wanna do something
child eat
proxy dont wanna do something
child run

可以看到, 代理动了, 它也拦截到了不是GirlFriend的类的方法(因为它对所有Object类有效)

(5)最后我们来作个死, 用装饰器的方式使用它

     既然我们看到了, 动态代理在使用方式上, 似乎和装饰器模式有点相似, 那我们用使用装饰器的方式来使用它,  这里我又实现了另一个CGLIB动态代理类HealthHandle, 代码参考上面

        GirlProxy gp = new GirlProxy();
        HealthHandle hh = new HealthHandle(); //另一个动态代理类
        Child child = (Child) hh.getProxyInstance(gp.getProxyInstance( new Child()));
        child.breath();
        child.eat();
        child.run();

得到的结果呢?

net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
Caused by: java.lang.ClassFormatError: Duplicate method name&signature in class file DesignPattern/ProxyPattern/CGlibPattern/Child$$EnhancerByCGLIB$$edb29237$$EnhancerByCGLIB$$a32ddec8
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
    ... 42 more

emmmmmmmm 大概懂什么意思,就是说不出来,总之就是,行不通的意思....

最后是,官方推荐对委托方法进行调用时, 使用MethodProxy,①官方说速度比较快 ②在intercept内调用委托类方法时不用保存委托对象引用

以上内容参考自最上面的博客, 加上部分自己的理解

猜你喜欢

转载自www.cnblogs.com/wqq-blog/p/10657173.html