1. 程式人生 > >代理模式和裝飾器模式區別

代理模式和裝飾器模式區別

代理模式與裝飾器模式有何區別?

我想有必要對此問題談一下我的個人理解,若有誤導的之處,還請大家指正!

代理模式(Proxy 模式)可理解為:我想做,但不能做,我需要有一個能幹的人來幫我做。

裝飾器模式(Decorator 模式)可理解為:我想做,但不能做,我需要有各類特長的人來幫我做,但我有時只需要一個人,有時又需要很多人。

它們的區別就是,Proxy 模式需要的是一個能人,而 Decorator 模式需要的是一個團隊。

有些情況下,擁有了一個團隊,會更加利於工作分工,而不至於將所有的事情,都讓這個能人來幹,他終將有一天會 hold 不住的。但有些情況下,人多了反而不好,只需要一個能人就行了。

如果這個比喻不太恰當的話,我就要拿出我的殺手鐗了,用程式碼來說話。

我們先來回憶一下這兩段經典的程式碼,一個介面,一個它的實現類。

public interface Greeting {

    void sayHello(String name);
}
public class GreetingImpl implements Greeting {

    @Override
    public void sayHello(String name) {
        System.out.println("Hello! " + name);
    }
}

可以使用 Proxy 類來代理 GreetingImpl 類做點事情:

public class GreetingProxy implements Greeting {

    private GreetingImpl greetingImpl;

    public GreetingProxy(GreetingImpl greetingImpl) {
        this.greetingImpl = greetingImpl;
    }

    @Override
    public void sayHello(String name) {
        before();
        greetingImpl.sayHello(name);
    }

    private void before() {
        System.out.println("Before");
    }
}

只需保證 GreetingProxy 與 GreetingImpl 實現同一個介面 Greeting,並通過構造方法將 GreetingImpl 溫柔地射入 GreetingProxy 的身體之中,那麼,GreetingProxy 就可以完全擁有 GreetingImpl 了。可以在幫它做正事兒之前,先乾點別的事情,比如這裡的 before() 方法。想幹點什麼就乾點什麼,只要您喜歡,它就喜歡。(此處省略一千字)

以上就是 Proxy 模式,可以認為 GreetingProxy 包裝了 GreetingImpl,那麼,我們就應該怎樣來使用呢?

public class ClientProxy {

    public static void main(String[] args) {
        Greeting greeting = new GreetingProxy(new GreetingImpl());
        greeting.sayHello("Jack");
    }
}

很爽吧?下面用一張類圖來表達我此時此刻的感覺:

 

可見,GreetingProxy 是通過“組合”的方式對 GreetingImpl 進行包裝,並對其進行功能擴充套件。這樣,無需修改 GreetingImpl 的任何一行程式碼,就可以完成它想要做的事情。

說的高深一點,這就是“開閉原則”(可不是一開一閉的意思哦),它是設計模式中一條非常重要的原則,意思就是“對擴充套件開放,對修改封閉”。沒錯,我們確實是提供了 GreetingProxy 類來擴充套件 GreetingImpl 的功能,而並非去修改 GreetingImpl 原有的程式碼。這就是超牛逼的“開閉原則”了,每個開發人員都需要銘記在心!還需要知道的就是擴充套件並非只有“繼承”這一種方式,這裡用到的“組合”也是一種擴充套件技巧。

其實,以上使用 Proxy 模式實現了 AOP 理論中的 Before Advice(前置增強)功能。如果使用者現在來了一個需求,需要在 sayHello 完事之後再記錄一點操作日誌。那麼,我們此時最簡單的方法就是給 GreetingProxy 增加一個 after() 方法,程式碼如下:

public class GreetingProxy implements Greeting {

    private GreetingImpl greetingImpl;

    public GreetingProxy(GreetingImpl greetingImpl) {
        this.greetingImpl = greetingImpl;
    }

    @Override
    public void sayHello(String name) {
        before();
        greetingImpl.sayHello(name);
        after();
    }

    private void before() {
        System.out.println("Before");
    }

    private void after() {
        System.out.println("After");
    }
}

這樣做確實可以實現需求,但您要知道,需求是永無止境的,這個 Proxy 類將來可能會非常龐大,要乾的事情會越來越多。一下子是日誌記錄,一下子是事務控制,還有許可權控制,還有資料快取。把所有的功能都放在這個 Proxy 類中是不明智的,同時這也違反了“開閉原則”。

作為一個牛逼的架構師,有必要來點炫的東西,讓那幫程式設計師小弟們對您投來崇拜的目光。

用 Decorator 模式吧!

先來一張牛圖:

搞了一個抽象類 GreetingDecorator 出來,確實挺抽象的,它就是傳說中的“裝飾器”了,也實現了 Greeting 介面(與 Proxy 模式相同),但卻有兩點不同:

  1. 在裝飾器中不是組合實現類 GreetingImpl,而是組合它的介面 Greeting。
  2. 下面通過兩個 Decorator 的實現類(也就是具體裝飾器),來提供多種功能的擴充套件。

我們不再需要一個能人,而需要一個團隊!

如果要加入日誌記錄功能,可以搞一個日誌記錄的裝飾器;如果要加入事務控制功能,也可以再搞一個事務控制的裝飾器;...

想怎麼裝飾就怎麼裝飾,這就像您買了一套新房,現在都是毛坯的,您可以刷漆,也可以貼紙,還可以畫畫,當然可以又刷漆、又貼紙、又畫畫。

屁話少說,上程式碼吧!

先來看看這個裝飾器:

public abstract class GreetingDecorator implements Greeting {

    private Greeting greeting;

    public GreetingDecorator(Greeting greeting) {
        this.greeting = greeting;
    }

    @Override
    public void sayHello(String name) {
        greeting.sayHello(name);
    }
}

以上是一個很乾淨的裝飾器,沒有任何的增強邏輯,只是簡單的通過構造方法射入了 Greeting 物件,然後呼叫它自己的 sayHello() 方法,感覺啥也沒幹一樣。

當然,GreetingDecorator 只是一個抽象的裝飾器,要想真正使用它,您得去繼承它,實現具體的裝飾器才行。

第一個具體裝飾器 GreetingBefore:

public class GreetingBefore extends GreetingDecorator {

    public GreetingBefore(Greeting greeting) {
        super(greeting);
    }

    @Override
    public void sayHello(String name) {
        before();
        super.sayHello(name);
    }

    private void before() {
        System.out.println("Before");
    }
}

第二個具體裝飾器 GreetingAfter:

public class GreetingAfter extends GreetingDecorator {

    public GreetingAfter(Greeting greeting) {
        super(greeting);
    }

    @Override
    public void sayHello(String name) {
        super.sayHello(name);
        after();
    }

    private void after() {
        System.out.println("After");
    }
}

需要注意的是,在具體裝飾器的構造方法中呼叫了父類的構造方法,也就是把 Greeting 例項射進去了。在具體裝飾器中,完成自己應該完成的事情。真正做到了各施其責,而不是一人包攬。

我們可以這樣來用裝飾器:

public class ClientDecorator {

    public static void main(String[] args) {
        Greeting greeting = new GreetingAfter(new GreetingBefore(new GreetingImpl()));
        greeting.sayHello("Jack");
    }
}

先 new GreetingImpl,再 new GreetingBefore,最後 new GreetingAfter。一層裹一層,就像洋蔥一樣!但不同的是,裹的順序是可以交換,比如,先 new GreetingAfter,再 new GreetingBefore。

這種建立物件的方式是不是非常眼熟呢?沒錯!在 JDK 的 IO 包中也有類似的現象。

比如:想讀取一個二進位制檔案,可以這樣獲取一個輸入流:

InputStream input = new DataInputStream(new BufferedInputStream(new FileInputStream("C:/test.exe")));

其實看看 IO 的類圖,就一目瞭然了,它就用到了 Decorator 模式:

IO 包是一個很強大的包,為了使表達更加簡化,以上僅提供了 IO 中的部分類。

到此,想必您已經瞭解到 Proxy 模式與 Decorator 模式的本質區別了吧?

這裡兩個模式都是對類的包裝,在不改變類自身的情況下,為其新增特定的功能。若這些功能比較單一,可考慮使用 Proxy 模式,但對於功能較多且需動態擴充套件的情況下,您不妨嘗試一下 Decorator 模式吧!

如果本文對您有幫助,那就頂起來吧!

轉自:https://my.oschina.net/huangyong/blog/162655