設計模式-7.裝飾者模式

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());
	    }
}

運行結果:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章