1.裝飾器模式(Decorator Pattern)允許向一個現有的對象添加新的功能,同時又不改變其結構。這種類型的設計模式屬於結構型模式,它是作爲現有的類的一個包裝。這種模式創建了一個裝飾類,用來包裝原有的類,並在保持類方法簽名完整性的前提下,提供了額外的功能。
2.作用:動態地給一個對象添加一些額外的職責,裝飾者提供了比繼承更有彈性的替代方案。
3.我覺得我寫的不會比這個更好了:https://www.runoob.com/design-pattern/decorator-pattern.html
一、星巴茲咖啡的故事
我們通過一個生動有趣的例子來引出我們今天的主角--裝飾者模式。
1、現在呢,有一個咖啡館,它有一套自己的訂單系統,當顧客來咖啡館的時候,可以通過訂單系統來點自己想要的咖啡。他們原先的設計是這樣子的:cost()計算價格
2、此時、咖啡館爲了吸引更多的顧客,需要在訂單系統中允許顧客選擇加入不同調料的咖啡,例如:蒸奶(Steamed Milk)、豆漿(Soy)、摩卡(Mocha,也就是巧克力風味)或覆蓋奶泡。星巴茲會根據所加入的調料收取不同的費用。所以訂單系統必須考慮到這些調料部分。
下面是他們的第一次嘗試:
這種設計肯定是不行的,簡直分分鐘把人逼瘋的節奏,有木有!
3、這時,有個人提出了新的方案,利用實例變量和繼承,來追蹤這些調料。
具體爲:先從Beverage基類下手,加上實例變量代表是否加上調料(牛奶、豆漿、摩卡、奶泡……),
這種設計雖然滿足了現在的需求,但是我們想一下,如果出現下面情況,我們怎麼辦,
①、調料價錢的改變會使我們更改現有代碼。
②、一旦出現新的調料,我們就需要加上新的方法,並改變超類中的cost()方法。
③、以後可能會開發出新飲料。對這些飲料而言(例如:冰茶),某些調料可能並不適合,但是在這個設計方式中,Tea(茶)子類仍將繼承那些不適合的方法,例如:hasWhip()(加奶泡)。
④、萬一顧客想要雙倍摩卡咖啡,怎麼辦?
很明顯,上面的設計並不能夠從根本上解決我們所碰到的問題。並且這種設計違反了 開放關閉原則(類應該對擴展開放,對修改關閉。)。
那我們怎麼辦呢?好啦,裝飾者可以非常完美的解決以上的所有問題,讓我們有一個設計非常nice的咖啡館。
二、定義
裝飾者模式:動態地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。
三、實現
下面我們就利用裝飾者模式來實現一個全新的咖啡館。
1、我們要以飲料爲主體,然後在運行時以調料來“裝飾”(decorate)飲料。比方說,如果顧客想要摩卡和奶泡深焙咖啡,那麼,要做的是:
①、拿一個深焙咖啡(DarkRoast)對象
②、以摩卡(Mocha)對象裝飾它
③、以奶泡(Whip)對象裝飾它
④、調用cost()方法,並依賴委託(delegate)將調料的價錢加上去。
好了!但是如何“裝飾”一個對象,而“委託”又要如何與此搭配使用呢?那就是把裝飾者對象當成“包裝者”。讓我們看看這是如何工作的:
2、設計
我們將我們所知道的寫下來:
①、裝飾者和被裝飾對象有相同的超類型。
②、你可以用一個或多個裝飾者包裝一個對象。
③、既然裝飾者和被裝飾對象有相同的超類型,所以在任何需要原始對象(被包裝的)的場合,可以用裝飾過的對象代替它。
④、裝飾者可以在所委託被裝飾者的行爲之前與 / 或之後,加上自己的行爲,以達到特定的目的。
⑤、對象可以在任何時候被裝飾,所以可以在運行時動態地、不限量地用你喜歡的裝飾者來裝飾對象。
下面,我們來看一下裝飾者模式的類圖:
利用裝飾者模式來實現我們的訂單系統的類圖:
我們已經將訂單系統設計完畢,下面讓我們用代碼來實現它吧!
3、代碼實現:
飲料抽象類:
package com.newland.draw.decorator1;
import java.math.BigDecimal;
/**
* 飲料抽象類,使用接口也可以
* @author lx
*
*/
public abstract class Beverage {
String description = "Unknown Beverage";
public String getDescription() {
return description;
}
/**
* 計算飲料價格
* @return
*/
public abstract BigDecimal cost();
}
具體的飲料類:深焙咖啡類
package com.newland.draw.decorator1;
import java.math.BigDecimal;
/**
* 深焙咖啡類
* @author lx
*
*/
public class DarkRoast extends Beverage{
/**
* 說明他是DarkRoast飲料(一種具體飲料)
*/
public DarkRoast() {
description = "DarkRoast";
}
//返回DarkRoast(深焙咖啡)的價格
@Override
public BigDecimal cost() {
// TODO Auto-generated method stub
return new BigDecimal("3.00");
}
}
具體的飲料類:低咖啡因類咖啡
package com.newland.draw.decorator1;
import java.math.BigDecimal;
/**
* 低咖啡因類咖啡(一種具體飲料)
* @author lx
*
*/
public class Decaf extends Beverage{
/**
* 說明他是Decaf飲料
*/
public Decaf() {
description = "Decaf";
}
//返回Decaf(低咖啡因類咖啡)的價格
@Override
public BigDecimal cost() {
// TODO Auto-generated method stub
return new BigDecimal("4.00");
}
}
具體的飲料類:濃縮類咖啡
package com.newland.draw.decorator1;
import java.math.BigDecimal;
/**
* 濃縮類咖啡(一種具體飲料)
* @author lx
*
*/
public class Espresso extends Beverage{
/**
* 說明他是Espresso飲料
*/
public Espresso() {
description = "Espresso";
}
//返回Decaf(濃縮類咖啡)的價格
@Override
public BigDecimal cost() {
// TODO Auto-generated method stub
return new BigDecimal("2.00");
}
}
以上是被裝飾的類
調料裝飾者抽象類
package com.newland.draw.decorator1;
public abstract class CondimentDecorator extends Beverage{
/**
* 所有的調料裝飾者都必須重新實現getDescription()方法
* 這樣才能夠用遞歸的方式來得到所選飲料的整體描述
*
* @return
*/
public abstract String getDescription();
}
摩卡調料類:
package com.newland.draw.decorator1;
import java.math.BigDecimal;
/**
* 摩卡調料類
* @author lx
*
*/
public class Mocha extends CondimentDecorator{
//用一個實例變量記錄飲料,也就是被裝飾者
Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
//在原來飲料的基礎上添加上Mocha描述(原來的飲料加入Mocha調料,被Mocha調料裝飾)
@Override
public String getDescription() {
// TODO Auto-generated method stub
return beverage.getDescription()+",Mocha";
}
//在原來飲料的基礎上加上Mocha的價格(原來的飲料加入Mocha調料,被Mocha調料裝飾)
@Override
public BigDecimal cost() {
// TODO Auto-generated method stub
return new BigDecimal("0.2").add(beverage.cost());
}
}
豆漿調料類:
package com.newland.draw.decorator1;
import java.math.BigDecimal;
/**
* 豆漿調料類
* @author lx
*
*/
public class Soy extends CondimentDecorator{
//用一個實例變量記錄飲料,也就是被裝飾者
Beverage beverage;
public Soy(Beverage beverage) {
this.beverage = beverage;
}
//在原來飲料的基礎上添加上Soy描述(原來的飲料加入Soy調料,被Soy調料裝飾)
@Override
public String getDescription() {
// TODO Auto-generated method stub
return beverage.getDescription()+",Soy";
}
//在原來飲料的基礎上加上Soy的價格(原來的飲料加入Soy調料,被Soy調料裝飾)
@Override
public BigDecimal cost() {
// TODO Auto-generated method stub
return new BigDecimal("0.3").add(beverage.cost());
}
}
奶泡調料類:
package com.newland.draw.decorator1;
import java.math.BigDecimal;
/**
* 奶泡調料類
* @author lx
*
*/
public class Whip extends CondimentDecorator{
//用一個實例變量記錄飲料,也就是被裝飾者
Beverage beverage;
public Whip(Beverage beverage) {
this.beverage = beverage;
}
//在原來飲料的基礎上添加上Whip描述(原來的飲料加入Whip調料,被Whip調料裝飾)
@Override
public String getDescription() {
// TODO Auto-generated method stub
return beverage.getDescription()+",Whip";
}
//在原來飲料的基礎上加上Whip的價格(原來的飲料加入Whip調料,被Whip調料裝飾)
@Override
public BigDecimal cost() {
// TODO Auto-generated method stub
return new BigDecimal("0.4").add(beverage.cost());
}
}
咖啡館(模擬顧客下單):
package com.newland.draw.decorator1;
/**
* 咖啡館模擬顧客下單
* @author lx
*
*/
public class StarbuzzCoffee {
public static void main(String[] args) {
//訂一杯Espresso(2.00),不需要調料,打印出它的描述與價錢。
Beverage beverage = new Espresso();
System.out.println("Description: " + beverage.getDescription() + " $" + beverage.cost());
//製造出一個DarkRoast(3.00)對象,用Mocha(0.2)裝飾它,用第二個Mocha(0.2)裝飾它,用Whip(0.4)裝飾它,打印出它的描述與價錢。
Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println("Description: " + beverage2.getDescription() + " $" + beverage2.cost());
//再來一杯調料爲豆漿(Soy 0.3)、摩卡(Mocha 0.2)、奶泡(Whip 0.4)的Decaf(低咖啡因咖啡 4.00),打印出它的描述與價錢。
Beverage beverage3 = new Decaf();
beverage3 = new Soy(beverage3);
beverage3 = new Mocha(beverage3);
beverage3 = new Whip(beverage3);
System.out.println("Description: " + beverage3.getDescription() + " $" + beverage3.cost());
}
}
運行結果:
從以上,我們可以知道,當我們使用繼承,導致子類膨脹,我們不想增加很多子類的情況下,將具體功能職責劃分,同時繼承裝飾者超類,動態地給一個對象添加一些額外的職責便實現了我們的裝飾者模式。
四、優缺點
1、優點:裝飾類和被裝飾類可以獨立發展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式可以動態擴展一個實現類的功能。
2、缺點:多層裝飾比較複雜。
五、使用場景:
1、擴展一個類的功能。
2、動態增加功能,動態撤銷。
實際使用:這裏我們說一下,在java中I/O便使用了裝飾者模式。
六、裝飾者用到的設計原則:
1、多用組合,少用繼承。
2、對擴展開放,對修改關閉。
七 上面是原作者的內容。我將代碼改爲接口的形式
飲料接口類,這裏我把描述也作爲一個接口方法實現
package com.newland.draw.decorator4;
import java.math.BigDecimal;
/**
* 飲料接口類
* @author lx
*
*/
public interface Beverage {
public String getDescription();
/**
* 計算飲料價格
* @return
*/
public BigDecimal cost();
}
深焙咖啡類
package com.newland.draw.decorator4;
import java.math.BigDecimal;
/**
* 深焙咖啡類
* @author lx
*
*/
public class DarkRoast implements Beverage{
/**
* 說明他是DarkRoast飲料(一種具體飲料)
*/
//返回DarkRoast(深焙咖啡)的價格
@Override
public BigDecimal cost() {
// TODO Auto-generated method stub
return new BigDecimal("3.00");
}
@Override
public String getDescription() {
// TODO Auto-generated method stub
return "DarkRoast";
}
}
低咖啡因類咖啡類
package com.newland.draw.decorator4;
import java.math.BigDecimal;
/**
* 低咖啡因類咖啡(一種具體飲料)
* @author lx
*
*/
public class Decaf implements Beverage{
/**
* 說明他是Decaf飲料
*/
//返回Decaf(低咖啡因類咖啡)的價格
@Override
public BigDecimal cost() {
// TODO Auto-generated method stub
return new BigDecimal("4.00");
}
@Override
public String getDescription() {
// TODO Auto-generated method stub
return "Decaf";
}
}
濃縮類咖啡類
package com.newland.draw.decorator4;
import java.math.BigDecimal;
/**
* 濃縮類咖啡(一種具體飲料)
* @author lx
*
*/
public class Espresso implements Beverage{
/**
* 說明他是Espresso飲料
*/
//返回Decaf(濃縮類咖啡)的價格
@Override
public BigDecimal cost() {
// TODO Auto-generated method stub
return new BigDecimal("2.00");
}
@Override
public String getDescription() {
// TODO Auto-generated method stub
return "Espresso";
}
}
所有調料類的父類
package com.newland.draw.decorator4;
import java.math.BigDecimal;
public class CondimentDecorator implements Beverage{
Beverage beverage;
/**
* 所有調料類繼承此類
*
*
* @return
*/
public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
// TODO Auto-generated method stub
return beverage.getDescription();
}
@Override
public BigDecimal cost() {
// TODO Auto-generated method stub
return beverage.cost();
}
}
裝飾者類: 摩卡,豆漿,奶泡 調料類
package com.newland.draw.decorator4;
import java.math.BigDecimal;
/**
* 摩卡調料類
* @author lx
*
*/
public class Mocha extends CondimentDecorator{
public Mocha(Beverage beverage) {
super(beverage);
// TODO Auto-generated constructor stub
}
//在原來飲料的基礎上添加上Mocha描述(原來的飲料加入Mocha調料,被Mocha調料裝飾)
@Override
public String getDescription() {
// TODO Auto-generated method stub
return super.getDescription()+",Mocha";
}
//在原來飲料的基礎上加上Mocha的價格(原來的飲料加入Mocha調料,被Mocha調料裝飾)
@Override
public BigDecimal cost() {
// TODO Auto-generated method stub
return new BigDecimal("0.2").add(super.cost());
}
}
package com.newland.draw.decorator4;
import java.math.BigDecimal;
/**
* 豆漿調料類
* @author lx
*
*/
public class Soy extends CondimentDecorator{
//用一個實例變量記錄飲料,也就是被裝飾者
public Soy(Beverage beverage) {
super(beverage);
// TODO Auto-generated constructor stub
}
//在原來飲料的基礎上添加上Soy描述(原來的飲料加入Soy調料,被Soy調料裝飾)
@Override
public String getDescription() {
// TODO Auto-generated method stub
return beverage.getDescription()+",Soy";
}
//在原來飲料的基礎上加上Soy的價格(原來的飲料加入Soy調料,被Soy調料裝飾)
@Override
public BigDecimal cost() {
// TODO Auto-generated method stub
return new BigDecimal("0.3").add(beverage.cost());
}
}
package com.newland.draw.decorator4;
import java.math.BigDecimal;
/**
* 奶泡調料類
* @author lx
*
*/
public class Whip extends CondimentDecorator{
public Whip(Beverage beverage) {
super(beverage);
// TODO Auto-generated constructor stub
}
//在原來飲料的基礎上添加上Whip描述(原來的飲料加入Whip調料,被Whip調料裝飾)
@Override
public String getDescription() {
// TODO Auto-generated method stub
return beverage.getDescription()+",Whip";
}
//在原來飲料的基礎上加上Whip的價格(原來的飲料加入Whip調料,被Whip調料裝飾)
@Override
public BigDecimal cost() {
// TODO Auto-generated method stub
return new BigDecimal("0.4").add(beverage.cost());
}
}
咖啡館類 模擬顧客下單:
package com.newland.draw.decorator4;
/**
* 咖啡館模擬顧客下單
* @author lx
*
*/
public class StarbuzzCoffee {
public static void main(String[] args) {
//訂一杯Espresso(2.00),不需要調料,打印出它的描述與價錢。
Beverage beverage = new Espresso();
System.out.println("Description: " + beverage.getDescription() + " $" + beverage.cost());
//製造出一個DarkRoast(3.00)對象,用Mocha(0.2)裝飾它,用第二個Mocha(0.2)裝飾它,用Whip(0.4)裝飾它,打印出它的描述與價錢。
Beverage beverage2 = new DarkRoast();
Mocha mocha = new Mocha(beverage2);
Mocha mocha1 = new Mocha(mocha);
Whip whip = new Whip(mocha1);
System.out.println("Description: " + whip.getDescription() + " $" + whip.cost());
//再來一杯調料爲豆漿(Soy 0.3)、摩卡(Mocha 0.2)、奶泡(Whip 0.4)的Decaf(低咖啡因咖啡 4.00),打印出它的描述與價錢。
Beverage beverage3 = new Decaf();
beverage3 = new Soy(beverage3);
beverage3 = new Mocha(beverage3);
beverage3 = new Whip(beverage3);
System.out.println("Description: " + beverage3.getDescription() + " $" + beverage3.cost());
}
}
運行結果: