有些朋友們看過《Proxy 那點事兒》與《AOP 那點事兒》以後,提出了一個頗有表明性的問題: java
代理模式與裝飾器模式有何區別? 程序員
我想有必要對此問題談一下個人我的理解,如有誤導的之處,還請你們指正! 設計模式
代理模式(Proxy 模式)可理解爲:我想作,但不能作,我須要有一個能幹的人來幫我作。 緩存
裝飾器模式(Decorator 模式)可理解爲:我想作,但不能作,我須要有各種特長的人來幫我作,但我有時只須要一我的,有時又須要不少人。 架構
它們的區別就是,Proxy 模式須要的是一個能人,而 Decorator 模式須要的是一個團隊。 ide
有些狀況下,擁有了一個團隊,會更加利於工做分工,而不至於將全部的事情,都讓這個能人來幹,他終將有一天會 hold 不住的。但有些狀況下,人多了反而很差,只須要一個能人就好了。 this
若是這個比喻不太恰當的話,我就要拿出個人殺手鐗了,用代碼來講話。 .net
咱們先來回憶一下這兩段經典的代碼,一個接口,一個它的實現類。 設計
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 模式相同),但卻有兩點不一樣:
咱們再也不須要一個能人,而須要一個團隊!
若是要加入日誌記錄功能,能夠搞一個日誌記錄的裝飾器;若是要加入事務控制功能,也能夠再搞一個事務控制的裝飾器;...
想怎麼裝飾就怎麼裝飾,這就像您買了一套新房,如今都是毛坯的,您能夠刷漆,也能夠貼紙,還能夠畫畫,固然能夠又刷漆、又貼紙、又畫畫。
屁話少說,上代碼吧!
先來看看這個裝飾器:
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 模式吧!
若是本文對您有幫助,那就頂起來吧!