代理模式和裝飾器模式區別
代理模式與裝飾器模式有何區別?
我想有必要對此問題談一下我的個人理解,若有誤導的之處,還請大家指正!
代理模式(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 模式相同),但卻有兩點不同:
- 在裝飾器中不是組合實現類 GreetingImpl,而是組合它的介面 Greeting。
- 下面通過兩個 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