Java設計模式之裝飾者模式

要實現裝飾者模式,注意一下幾點內容:

1.裝飾者類要實現真實類同樣的接口

2.裝飾者類內有一個真實對象的引用(可以通過裝飾者類的構造器傳入)

3.裝飾類對象在主類中接受請求,將請求發送給真實的對象(相當於已經將引用傳遞到了裝飾類的真實對象)

4.裝飾者可以在傳入真實對象後,增加一些附加功能(因為裝飾對象和真實對象都有同樣的方法,裝飾對象可以添加一定操作在調用真實對象的方法,或者先調用真實對象的方法,再添加自己的方法)

5.不用繼承,

先用實例說話,最後再具體裝飾者模式

假設要製造添加甜蜜素和著色劑的饅頭:

1.需要生產一個正常饅頭

2.為節省成本(不使用玉米麵),使用染色劑加入到正常饅頭中

3.和麵,最後生產出染色饅頭

一..先實現做麵包的接口

IBread接口包括準備材料,和麵,蒸饅頭,加工饅頭(即調用前面三個步驟)

 1 package 裝飾模式;
2
3 public interface IBread {
4
5 public void prepair();
6
7 public void kneadFlour();
8
9 public void steamed();
10
11 public void process();
12 }

二.製作正常饅頭

 1 package 裝飾模式;
2
3 public class NormalBread implements IBread{
4
5 @Override
6 public void prepair() {
7
8 System.out.println("準備麵粉,水以及發酵粉...");
9 }
10
11 @Override
12 public void kneadFlour() {
13
14 System.out.println("和麵...");
15 }
16
17 @Override
18 public void steamed() {
19
20 System.out.println("蒸饅頭...香噴噴的饅頭出爐了");
21 }
22
23 @Override
24 public void process() {
25
26 prepair();
27 kneadFlour();
28 steamed();
29 }
30

31 }

三.定義出製作麵包的抽象類

抽象類實現了IBread這個製作麵包的接口,同時包含IBread接口的實例

對應上述的第2個注意點:裝飾者類內有一個真實對象的引用

 1 package 裝飾模式;
2
3 public abstract class AbstractBread implements IBread {
4
5 private final IBread bread;
6
7 public AbstractBread(IBread bread) {
8 super();
9 this.bread = bread;
10 }
11 @Override
12 public void prepair() {
13 this.bread.prepair();
14 }
15 @Override
16 public void kneadFlour() {
17 this.bread.kneadFlour();
18 }
19 @Override
20 public void steamed() {
21 this.bread.steamed();
22 }
23
24 @Override
25 public void process() {
26 prepair();
27 kneadFlour();
28 steamed();
29 }
30
31 }

四.生產有著色劑的"玉米饅頭"

繼承AbstarctBread類,所以可以有選擇的覆蓋正常生產饅頭的方法,並添加原有方法原來的信息,同時也可以添加自己的方法

裝飾者模式中這裡最關鍵,

對應上述的第1個注意點:裝飾者類要實現真實類同樣的接口

 1 package 裝飾模式;
2
3 public class CornDecorator extends AbstractBread{
4
5 public CornDecorator(IBread bread) {
6
7 super(bread);
8 }
9
10 public void paint(){
11
12 System.out.println("添加檸檬黃的著色劑");
13 }
14 @Override
15 public void kneadFlour() {
16 //添加著色劑後和麵
17 this.paint();
18 super.kneadFlour();
19 }
20
21
22 }

五.生產有甜蜜素的"甜饅頭"

實現與第四部一樣

 1 package 裝飾模式;
2
3 public class SweetDecorator extends AbstractBread {

4
5 public SweetDecorator(IBread bread) {
6
7 super(bread);
8 }
9
10 public void paint(){
11
12 System.out.println("添加甜蜜素...");
13 }
14
15 @Override
16 public void kneadFlour() {
17 //添加甜蜜素後和麵
18 this.paint();
19 super.kneadFlour();
20 }
21
22 }

六.開始製作添加甜蜜素和著色劑的饅頭

 1 package 裝飾模式;
2
3 public class Client {
4
5 public static void main(String[] args) {
6
7 System.out.println("=======開始裝飾饅頭");
8 IBread normalBread = new NormalBread();
9 normalBread = new SweetDecorator(normalBread);
10 normalBread = new CornDecorator(normalBread);
11 normalBread.process();
12 System.out.println("=======裝飾饅頭結束");
13 }
14 }

七.輸出

1 =======開始裝飾饅頭
2 準備麵粉,水以及發酵粉...
3 添加檸檬黃的著色劑

4 添加甜蜜素...
5 和麵...
6 蒸饅頭...香噴噴的饅頭出爐了
7 =======裝飾饅頭結束

裝飾者模式中的4個角色

(1)被裝飾者抽象Component:是一個接口或者抽象類,定義最核心的對象,這個類是裝飾者的基類,例如IBread接口

(2)被裝飾者具體實現ConcreteComponent:這是Component接口或抽象類的實現,例如本例中的NormalBread

(3)裝飾者Decorator:一般是抽象類,實現Component,它裡面必然有一個指向Component的引用,例如本例中AbstractBread

(4)裝飾者實現ConcreteDecorator1和ConcreteDecorator2:用來裝飾最基本的類,如本例中的CornDecorator,

JDK中的裝飾者模式

java.io中很多使用了裝飾者模式

舉個例子:FilterInputStream繼承(實現)了InputStream,同時,BufferedInputStream繼承了FilterInputStream,

1,被裝飾者抽象組件:即最頂層的基類InputStream

2.被裝飾者具體實現ConcreteComponent:FileInputStream和FileOutputStream就是它的實現

3.裝飾者Decorator:FilterInputStream中有一個InputStream的實例和構造方法傳入InputStream對象

protected volatile InputStream in;

protected FilterInputStream(InputStream in) {

this.in = in;

}

4.裝飾者實現:在 BufferedInputStream 中有構造方法傳入InputStream對象,實現了裝飾

public BufferedInputStream(InputStream in, int size) {

super(in);

if (size <= 0) {

throw new IllegalArgumentException("Buffer size <= 0");

}

buf = new byte[size];

}

這個構造方法,對比上面的做麵包流程,可以驚奇的發現是一模一樣的. (可以將)

1.InputStream-->IBread

(這裡就是InputStream,沒什麼好說的)

2.FileInputStream-->NormalBread

3.FilterInputStream-->AbstractBread

(實現Component,這裡是InputStram.它裡面必然有一個指向Component的引用,這個引用就是InputStream的實例)

4. BufferedInputStream --> CornDecorator

(裝飾者實現,:用來裝飾最基本的類,對I傳入的nputStream進行了裝飾)

(BufferedInputStream 的super(in)就像CornDecorator中的kneadFlour()方法,也有super.kneadFlour();,只是這裡IO流中用在了構造方法 )

這就是JDK中的裝飾者模式

不用繼承方式實現裝飾者模式的原因(以此例為說明對象,)

1.如果只是單獨的添加色素或者甜蜜素確實是可以做到的,只需要將CornDecorator繼承NormalBread 和SweetDecorator 繼承NormalBread ,這樣也能夠覆蓋正常製作麵包的流程,添加附加的功能實現單獨製作"玉米饅頭"和"甜饅頭".

2.如此一來,如果我們要製作甜玉米饅頭(這裡加點先添加甜色素,再添加玉米色素),只需要先SweetDecorator繼承 NormalBread,然後CornSweetDecorator 再用繼承 CornDecorator ,這樣似乎是沒有問題的.

2.但是想想以下的情況,如果我們希望能夠在添加在添加甜色素和玉米色素中間還要加入洋蔥,這要怎麼做,難道說又用先SweetDecorator繼承 NormalBread,然後OnionSweetDecorator繼承SweetDecorator,最後再用CornOnionSweetDecorator 繼承OnionSweetDecorator??

顯然是不可能的,這樣會導致原來代碼的複用性低,而且形成了冗餘的繼承體系

4.使用上述實例的方法完全克服了這個問題,要實現添加洋蔥,只需要實現和SweetDecorator 類似的步驟即可,最後在Client類中傳入就可以實現這個功能

使用場合

1.需要為某個現有對象添加一個新的功能或職責時,可以考慮使用裝飾者模式

2.某個對象的職責經常發生變化或經常需要動態添加職責,避免為了適應這種變化造成的繼承擴展方式


分享到:


相關文章: