JAVA設計模式詳解(三)----------裝飾者模式


今天LZ帶給大家的是裝飾者模式,提起這個設計模式,LZ心里一陣激動,這是LZ學習JAVA以來接觸的第一個設計模式,也許也是各位接觸的第一個設計模式。記得當初老師在講IO的時候就提到過它:“是你還有你,一切拜托你。”沒錯,這就是裝飾者模式最簡潔的定義了。下面LZ引出標准的定義(出自百度百科):裝飾模式是在不必改變原類文件和使用繼承的情況下,動態的擴展一個對象的功能。它是通過創建一個包裝對象,也就是裝飾來包裹真實的對象。

ex:工廠制作上衣,白上衣是30,藍上衣40,紅上衣50,另外還有往衣服上綉的圖案,花圖案需10元,草圖案需5元。現在工廠需要計算出成本價,要求設計出這個實現代碼。

我們初看這題有什么思路,難道要設計出5個類嗎?這顯然不行,如果工廠老板突然讓加一個新的圖案,豈不是“類爆炸”!這個時候,我們可能想到了用另一種實現方式,把CLothes做為基類,然后設置whiteClothes,

blueClothes ,redClothes 為三個布爾類型變量,在基類里設置cost方法,然后圖案設置為子類繼承它,各自重寫cost方法。這樣就把原本的五個類縮小成了三個。
//白上衣是30,藍上衣40,紅上衣50,另外還有往衣服上綉的圖案,花圖案需10元,草圖案需5元
public  class Clothes { private int cost; private boolean whiteClothes = false; private boolean blueClothes = false; private boolean redClothes = false; public  int cost(){ if(whiteClothes){ cost += 30; } if(blueClothes){ cost += 40; } if(redClothes){ cost += 50; } return cost; } public int getCost() { return cost; } public void setCost(int cost) { this.cost = cost; } public boolean isWhiteClothes() { return whiteClothes; } public void setWhiteClothes(boolean whiteClothes) { this.whiteClothes = whiteClothes; } public boolean isBlueClothes() { return blueClothes; } public void setBlueClothes(boolean blueClothes) { this.blueClothes = blueClothes; } public boolean isRedClothes() { return redClothes; } public void setRedClothes(boolean redClothes) { this.redClothes = redClothes; } } class FlowerPattern extends Clothes{ public  int cost(){ return 10+super.cost(); } } class GrassPattern extends Clothes{ public  int cost(){ return 5+super.cost(); } }

測試方法

public class TestDemo1 { public static void main(String[] args) { FlowerPattern flowerPattern = new FlowerPattern(); flowerPattern.setBlueClothes(true); System.out.println("藍上衣花圖案一共花費:"+flowerPattern.cost()); GrassPattern grassPattern = new GrassPattern(); grassPattern.setRedClothes(true); System.out.println("紅上衣草圖案一共花費:"+grassPattern.cost()); } }

 

這種寫法,相信看過LZ前兩篇文章的人一眼就看出了問題,我們雖然寫出了實現,但是由於類之間過於耦合,導致不利於維護,當我們添加一個新的顏色的上衣時,需要更改Clothes代碼,這就違反了我們的設計原則:類應該對擴展開發,對修改關閉,也就是我們常說的開閉原則。在這里,LZ帶着大家一起認識裝飾者模式,我們以上衣為主體,以圖案來“裝飾”上衣。比方說,如果商家要藍上衣花草圖案,那么,我們要做的是:

①拿一個藍色上衣對象

②以花圖案對象裝飾它

③以草圖案對象裝飾它

④調用cost()方法,並依賴委托將調料的價錢加上去

下面我們畫個圖更加詳細了解如何以裝飾者構造衣服訂單

①以BlueClothes對象開始,別忘了它繼承自clothes,且有一個用來計算上衣價錢的cost()方法

                     

②建立一個花圖案對象,並用它將BlueClothes對象包(wrap)起來。FlowerPattern對象是一個裝飾者,它的類型"反映"了它所裝飾的對象(本例中,就是Clothes)。所謂的"反映",指的是兩者類型一致。所以FlowerPattern也有一個cost()方法。通過多態,也可以把FlowerPattern所包裹的任何clothes當成是clothes(因為FlowerPattern是clothes的子類)

 

③接下來還要建立草圖案GrassPattern裝飾者,並用它將FlowerPattern對象包起來。別忘了,BlueClothes繼承自Clothes,且有一個cost()方法,用來計算衣服價錢

④這樣,計算總價錢通過調用最外圈裝飾者(GrassPattern)的cost()就可以辦得到。GrassPatternd cost()方法會先委托它裝飾的對象(也就是FlowerPattern)計算出價錢,然后再加上上衣的價錢。

現在,LZ來總結一下我們目前所指的的一切:

=。=裝飾者和被裝飾者對象有相同的基類。

=。=我們可以用一個或多個裝飾者包裝一個對象。

=。=既然裝飾者和被裝飾對象有相同的基類,所以在任何需要原始對象(被包裝的)的場合,可以用裝飾過的對象代替它。

=。=裝飾者可以在所委托被裝飾者的行為之前與/或之后,加上自己的行為,以達到特定的目的,

=。=對象可以在任何時候被裝飾,所以可以在運行時動態地、不限量地用你喜歡的裝飾者來裝飾對象

接下來我們先來看看裝飾者模式的說明:裝飾者模式動態地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。

下面我們來看下類圖:

到這里,大家可能會有些混淆:這里使用的是繼承不是組合?。其實,這么做的重點在於,裝飾者和被裝飾者必須是一樣的類型,也就是有共同的基類,這是相當關鍵的地方。在這里我們利用繼承達到“類型匹配”,而不是利用繼承獲得“行為”。那么這個行為又來自哪里?當我們將裝飾者與組件組合時,就是再加入新的行為,所得到的新行為,並不是繼承自超累,而是由組合對象得來的。

分支

這里,為了防止各位看不懂圖的結構,我們先切出一個分支來描述一下這張圖,當然如果各位已經看懂,也可以直接跳過這段分支,直接看下面LZ對一開始那個問題的分析。

 1,Component接口可以是接口也可以是抽象類,甚至是一個普通的父類(這個強烈不推薦,普通的類作為繼承體系的超級父類不易於維護)。

 2,裝飾器的抽象父類Decorator並不是必須的。

                 那么我們將上述標准的裝飾器模式,用我們熟悉的JAVA代碼給詮釋一下。首先是待裝飾的接口Component。

public interface Component { void method(); }

 接下來便是我們的一個具體的接口實現類,也就是俗稱的原始對象,或者說待裝飾對象。

public class ConcreteComponent implements Component{ public void method() { System.out.println("原來的方法"); } }

下面便是我們的抽象裝飾器父類,它主要是為裝飾器定義了我們需要裝飾的目標是什么,並對Component進行了基礎的裝飾。

public abstract class Decorator implements Component{ protected Component component; public Decorator(Component component) { super(); this.component = component; } public void method() { component.method(); } }

再來便是我們具體的裝飾器A和裝飾器B。

public class ConcreteDecoratorA extends Decorator{ public ConcreteDecoratorA(Component component) { super(component); } public void methodA(){ System.out.println("被裝飾器A擴展的功能"); } public void method(){ System.out.println("針對該方法加一層A包裝"); super.method(); System.out.println("A包裝結束"); } }
public class ConcreteDecoratorB extends Decorator{ public ConcreteDecoratorB(Component component) { super(component); } public void methodB(){ System.out.println("被裝飾器B擴展的功能"); } public void method(){ System.out.println("針對該方法加一層B包裝"); super.method(); System.out.println("B包裝結束"); } }

下面給出我們的測試類。我們針對多種情況進行包裝。

public class Main { public static void main(String[] args) { Component component =new ConcreteComponent();//原來的對象
        System.out.println("------------------------------"); component.method();//原來的方法
        ConcreteDecoratorA concreteDecoratorA = new ConcreteDecoratorA(component);//裝飾成A
        System.out.println("------------------------------"); concreteDecoratorA.method();//原來的方法
        concreteDecoratorA.methodA();//裝飾成A以后新增的方法
        ConcreteDecoratorB concreteDecoratorB = new ConcreteDecoratorB(component);//裝飾成B
        System.out.println("------------------------------"); concreteDecoratorB.method();//原來的方法
        concreteDecoratorB.methodB();//裝飾成B以后新增的方法
        concreteDecoratorB = new ConcreteDecoratorB(concreteDecoratorA);//裝飾成A以后再裝飾成B
        System.out.println("------------------------------"); concreteDecoratorB.method();//原來的方法
        concreteDecoratorB.methodB();//裝飾成B以后新增的方法
 } }

從這個例子中,各位應該對裝飾者模式的結構有了一定的了解,那么接下來我們解決之前的那個問題。

分支結束

接下來我們開始看最開始的問題代碼:

先從clothes下手,這里我們加一個description 用來描述的更詳細一下:

public abstract class Clothes { String description = "unknown Clothes"; public String getDescription(){ return description; } public abstract double cost(); }

接下來是裝飾者類,我們成為圖案Pattern類,也繼承自Clothes

 abstract class Pattern extends Clothes{ public abstract String getDescription(); }

有了兩個基類,我們為實現一些衣服,這里我們為了描述清楚,每個都寫構造方法:

class WhiteClothes extends Clothes{ public WhiteClothes(){ description = "WhiteClothes"; } @Override public double cost() { return 30; } } class BlueClothes extends Clothes{ public BlueClothes(){ description = "BlueClothes"; } @Override public double cost() { return 40; } } class RedClothes extends Clothes{ public RedClothes(){ description = "RedClothes"; } @Override public double cost() { return 50; } }

接下來我們開始寫圖案代碼,也就是具體裝飾者類,為了能夠跟上思路,LZ標了注釋

class FlowerPattern extends Pattern{//花圖案是一個裝飾者,讓它擴展自Pattern,而Pattern又擴展自Clothes //我們為了能讓FlowerPattern能夠引用clothes,用一個實例變量記錄衣服,也就是被裝飾者,然后在構造器中將其記錄在實例變量里
    private Clothes clothes; public FlowerPattern(Clothes clothes){ this.clothes = clothes; } @Override public String getDescription() { return clothes.getDescription() + ",FlowerPattern";//這里我們把圖案也描述出來
 } @Override public double cost() { return 10+clothes.cost();//我們把調用委托給被裝飾對象,以計算價錢,然后再加上FlowerPattern的價錢,得到最終結果
 } }

同理,我們寫出草圖案的具體實現:

class GrassPattern extends Pattern{//草圖案是一個裝飾者,讓它擴展自Pattern,而Pattern又擴展自Clothes //我們為了能讓FlowerPattern能夠引用clothes,用一個實例變量記錄衣服,也就是被裝飾者,然后在構造器中將其記錄在實例變量里
    private Clothes clothes; public GrassPattern(Clothes clothes){ this.clothes = clothes; } @Override public String getDescription() { return clothes.getDescription() + ",GrassPattern";//這里我們把圖案也描述出來
 } @Override public double cost() { return 5+clothes.cost();//我們把調用委托給被裝飾對象,以計算價錢,然后再加上GrassPattern的價錢,得到最終結果
 } }

最后,測試一下:

public class TestDemo2 { public static void main(String[] args) { Clothes clothes = new BlueClothes(); clothes = new FlowerPattern(clothes); clothes = new GrassPattern(clothes); System.out.println(clothes.getDescription()+"$"+clothes.cost()); } }

看到這里,LZ相信大家一定有些熟悉,記得我們在學習IO的時候曾學過包裝流,

其中FileInputStream是被裝飾的“組件”。

BufferedInputStream是一個具體的“裝飾者”,它加入兩種行為:利用緩沖輸入來改進性能利用一個readLine()方法(用來一次讀取一行文本輸入數據)來增強接口。

LineNumberInputStream也是一個具體的“裝飾者”。它加上了計算行數的能力

這里也引出了裝飾者模式的一個缺點,常常造成設計中有大量的小類,數量實在太多,可能會造成使用此API程序員的困擾。但是如果我們已經了解了裝飾者的工作原理,以后當使用別人的大量裝飾者的API時,就可以很容易辨別出他們的裝飾者類是如何組織的,以方便用包裝方式去的想要的行為。

 裝飾者模式就到此結束了,感謝大家的收看。

下期預告,單例模式。

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM