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
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時,就可以很容易辨別出他們的裝飾者類是如何組織的,以方便用包裝方式去的想要的行為。
裝飾者模式就到此結束了,感謝大家的收看。