設計模式 - 裝飾模式和代理模式

參考博客:http://www.javashuo.com/article/p-mjravmdr-br.htmlhtml

http://www.javashuo.com/article/p-tbgivpuw-u.htmljava

參考以上博客做出的小總結node

 

寫完下面的內容以後的總結:app

要區分裝飾模式和代理模式, 不能只從代碼結構上區分, 而更要從兩個模式的 使用目的  和 使用方式 區分ide

好比裝飾器模式的目的, 是爲了加強代碼 , 爲了讓裝飾以前的類具備更多的功能. 在使用方式上, 裝飾器能夠一層一層疊加使用性能

而代理模式 是爲了控制對被代理的對象的訪問的同時能使用其功能,  使用方式上, 使用者是看不到被代理的對象的測試

 

一.  裝飾模式this

   裝飾模式沒有被裝飾類的控制權, 裝飾類只是將被裝飾類的功能拓展, 兩個類都實現同一接口, 也就是裝飾類能夠裝飾實現了這一接口的任何其餘類spa

裝飾器模式的特色就是, 咱們經常將被裝飾類做爲參數傳遞進裝飾器中, 如IO流的各類裝飾器類.net

 

這裏咱們舉個栗子:

(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();
    }

 

作到這裏突然發現, 奇怪, 爲何和裝飾器同樣了呢, 爲啥我能夠見到我女友了呢?

我查詢動態代理和靜態代理的區別後, 看到了這個博客, 瞭解了區別: http://www.javashuo.com/article/p-yjktehxc-et.html

動態代理是爲了解決靜態代理的缺點: 每個代理類只能爲一個接口服務,若是想要轉爲加強另外一個接口, 則須要一個新的代理類, 這樣程序開發中必然會產生許多代理類

好比你只想測試全部接口的實現類的方法的性能, 可是你卻要爲每一個接口寫一個性能測試的代理類

所以,上面的基於接口實現類的動態代理就幫助你解決了這一問題, 由於它能夠爲任何Object類進行代理

 

2.2 基於子類的動態代理

   在通常狀況下, 咱們想加強的類多是沒有實現任何接口的普通類, 在這種狀況下, 要如何對其進行代理?

   如下取自博客:http://www.javashuo.com/article/p-mjravmdr-br.html

    假設目前有個項目,A公司寫了一個Computer類已經整合到某一個jar包裏面,這個類是個普通類沒有任何抽象角色(沒有實現任何接口)。可是B公司須要對Computer類內的方法進行增強、拓展。那B公司怎麼辦?目前只有兩個方法:一、拿A公司代碼反編譯,而後修改A公司裏面這個類的方法。 二、拿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內調用委託類方法時不用保存委託對象引用

 

以上內容參考自最上面的博客, 加上部分本身的理解

相關文章
相關標籤/搜索