Java 之 裝飾者模式

本章能夠成爲 「給愛用繼承的人一個全新的設計眼界」。咱們即將再度探討繼承濫用的問題。並在會在本章中學到如何使用對象組合的方式,作到運行時裝飾類。爲什麼?一旦你熟悉了裝飾的技巧。你將可以在不修改任何底層代碼的狀況下,給你的(或別人的)對象賦予新的職責。java

章節開頭,咱們看到了一個屌絲大叔端着一杯星巴茲的咖啡,一臉賤樣的想着,「曾經我覺得男子漢應該用繼承梳理一切,後來我領教到運行時擴展,遠比編譯時期的繼承威力大。」設計模式

一、笨方法

小子的學習方式是:先把星巴茲的問題看懂(業務難點),而後把章節拉到後邊,着手把代碼敲了一遍,這個時候回頭再來思考星巴茲遇到的業務難點,天然也就瞭然於胸。若是此時的你茫然無措,對這設計模式這本書有些迫不得已,不妨嘗試一下我這種笨拙的辦法?ide

二、星巴茲故事

好,你們都對於星巴茲這家咖啡店很是熟悉吧?我今天在「獲得」這個APP上閱讀了吳軍老師講的品味咖啡,卻是對於咖啡有了一點點認知,至少不會輕易以爲咖啡只是用來提神的苦水。若是你也想有一點點了解,不妨看看個人讀書筆記:http://www.jianshu.com/p/f9aa11449ab8(如何能夠,建議在獲得這個APP上訂閱吳軍老師的專欄吧,嘗試着另外一種生活的態度)函數

 

那好,說正題,咱們說一下星巴茲在中國迅速鋪開市場的時候,想要更新訂單系統時趕上的業務痛點:星巴茲目前有四種飲料,他們單獨售價都是固定好的,而此時由於業務拓展,爲了迎合市場客戶的需求,新增了幾種調料,後續可能會增長其餘的飲料種類也說不定哦。那麼,基於原有的訂單業務是怎麼實現的呢?學習

先來看原先的訂單系統的飲料類圖:優化

因此若是盲目的按照原先的繼承結構來實現,那麼星巴茲可能須要實現的類圖以下:this

這已經不是簡單的類爆炸了,若是接下來星巴茲在中國的業務持續飆升呢?整個訂單系統將龐大到沒法想象,已經能夠預料到,若是星巴茲的工程師不被逼瘋,大概也只能跑路了。由於維護近乎沒法維護的代碼,足矣讓工程師溜之大吉。spa

或許有沉迷繼承的傢伙會站出來,指着小子的鼻子說,只要在繼承的結構上優化,能夠解決這種問題:好比在Beverage基類上作處理,就能夠完美解決龐大類的問題,並且僅僅只須要五個類,不信你看看設計好的類圖:.net

乍一看,誒,彷佛真的能夠完美解決的耶?可是仔細想一想,這個繼承的結構是站在什麼角度上來考慮的?是站在既定的業務再也不擴展而設計的!好比此時要新增一種調料,是否是還要繼續修改基類?好比有一個客戶的口味很是重,他須要兩個份量的摩卡,系統怎麼實現呢?而且,假如星巴茲調查到競爭對手在買的一款綠茶在中國很是的暢銷,客戶正在源源不斷的流失,總部要求星巴茲分部必定要加上綠茶,可是綠茶的結構裏,不該該有摩卡,不該該有豆漿啊!因此,這個結構的實現並未從真正意義上解決系統痛點。若是有其餘的設計方案,咱們是否先考慮考慮軟件開發的系統設計原則?好比:設計

軟件開發的無上法典:開閉原則

關於開閉原則,這裏不加以贅述,若是不瞭解的童鞋,請猛烈戳下邊的連接:http://blog.csdn.net/zhengzhb/article/details/7296944(不是小子的筆記,可是寫的很好,雙手奉上)

 

既然繼承不能很好的解決問題,思考更優的解決方案以後,請跟小子一塊兒認識一下今天的主題——裝飾者模式。

前邊說道星巴茲的訂單系統擴展的問題,簡直讓人無語,聞風喪膽,那麼若是使用裝飾者模式是如何解決問題的呢?很簡單,思考——「把星巴茲提供的飲料(請自行過濾掉配料)做爲主體對象(被裝飾對象),而後把配料(裝飾對象)一步步裝配到飲料的主體對象上。」

好比: 我到了星巴茲咖啡店,點了一杯濃縮咖啡Espresso ,這個時候我想要加一些摩卡Mocha,再加一些豆漿 SoybeanMilk。因此這杯咖啡的價格應該是:Espresso + Mocha + SoybeanMilk。從一個主體Espresso裝飾上Mocha,再裝飾上SoybeanMilk,以下圖:

  那麼價格如何計算?

SoybeanMilk.cost() + (Mocha.cost() + Espresson.cost())

三、代碼實現

裝飾者模式定義:動態的將責任附加到對象上,若要擴展功能,裝飾者模式提供了比繼承更加彈性更加優越的替代方案;

另外一個軟件開發無上法典:組合優先考慮於繼承。

  如下是實現的代碼,注意Beverage與CondimentDecroator的關係

package cn.org.lennon.decorate;

import java.math.BigDecimal;


/**
 * 選擇head first書中的星巴茲案例,星巴茲是一件飲料店,旗下有多種飲料。
 * 
 * @author lennon
 * @time 2017年3月19日上午11:35:36
 * @className Beverage 飲料
 */
public abstract class Beverage {
	
	/**
	 * 飲料的價格
	 */
	protected BigDecimal price;
	
	/**
	 * 描述,咱們能夠稱之爲名稱 
	 */
	protected String description = "unkonw Beverage";
	
	/**
	 * 獲取到一種飲料的描述(好比名稱)
	 * 
	 * @return
	 */
	public String getDescrition() {
		return description;
	}
	
	/**
	 * 計算這杯飲料的價格
	 * 
	 * @return
	 */
	public abstract BigDecimal cost() ;
}
package cn.org.lennon.decorate;


/**
 * 星巴茲調料的抽象類,擴展至Berverage,由於咱們認爲調料其實也是飲料中的一種。
 * 
 * @author lennon
 * @time 2017年3月19日上午11:48:31
 *
 */
public abstract class CondimentDecoratore extends Beverage {
	
	/**
	 * 獲取調料的相關描述(咱們能夠認爲是名稱)
	 * 
	 * @return
	 */
	public abstract String getDescription() ;
	
}

因此,來看看被裝飾者(星巴茲咖啡店的主要飲料)如何實現吧:

package cn.org.lennon.decorate.mian;

import java.math.BigDecimal;

import cn.org.lennon.decorate.Beverage;

/**
 * 濃縮咖啡,是星巴茲的一種主要飲料
 * 
 * @author lennon
 * @time 2017年3月19日上午11:51:19
 *
 */
public class Espresso extends Beverage {
	
	/**
	 * 構造函數,初始化濃縮咖啡的描述(名稱), 以及它的價格
	 */
	public Espresso(BigDecimal price){
		this.description = "Espresso";
		this.price = price;
	}

	/**
	 * 它的金額數量是
	 */
	@Override
	public BigDecimal cost() {
		// TODO Auto-generated method stub
		return price;
	}

}

那麼,看看裝飾者(星巴茲咖啡店的調料)又是如何實現的吧:

package cn.org.lennon.decorate.condiment;

import java.math.BigDecimal;

import cn.org.lennon.decorate.Beverage;
import cn.org.lennon.decorate.CondimentDecoratore;


/**
 * 星巴茲的調料中,有一種叫作摩卡的調料
 * 
 * @author lennon
 * @time 2017年3月19日上午11:55:48
 *
 */
public class Mocha extends CondimentDecoratore {
	
	/**
	 * 飲料
	 */
	private Beverage beverage;
	
	/**
	 * 構造函數,初始化摩卡的裝飾在一杯飲料之上
	 * 
	 * @param beverage
	 */
	public Mocha(Beverage beverage, BigDecimal price){
		this.beverage 	= beverage;
		this.price 		= price;
	}

	@Override
	public String getDescription() {
		// TODO Auto-generated method stub
		return this.beverage.getDescrition() + " + Mocha";
	}

	@Override
	public BigDecimal cost() {
		// TODO Auto-generated method stub
		return this.price.add(this.beverage.cost());
	}

}

package cn.org.lennon.decorate.condiment;

import java.math.BigDecimal;

import cn.org.lennon.decorate.Beverage;
import cn.org.lennon.decorate.CondimentDecoratore;

/**
 * 星巴茲的調料中,有一種叫作豆漿的調料
 * 
 * @author lennon
 * @time 2017年3月19日下午12:04:04
 *
 */
public class SoybeanMilk extends CondimentDecoratore {

	/**
	 * 飲料
	 */
	private Beverage beverage;
	
	/**
	 * 構造函數,初始化奶油的裝飾在一杯飲料之上
	 * 
	 * @param beverage
	 */
	public SoybeanMilk(Beverage beverage, BigDecimal price){
		this.beverage 	= beverage;
		this.price 		= price;
	}
	
	@Override
	public String getDescription() {
		// TODO Auto-generated method stub
		return this.beverage.getDescrition() + " + SoybeamMilk";
	}

	@Override
	public BigDecimal cost() {
		// TODO Auto-generated method stub
		return this.price.add(this.beverage.cost());
	}

}

package cn.org.lennon.decorate.condiment;

import java.math.BigDecimal;

import cn.org.lennon.decorate.Beverage;
import cn.org.lennon.decorate.CondimentDecoratore;

/**
 * 星巴茲的調料中,有一種叫作奶油的調料
 * 
 * @author lennon
 * @time 2017年3月19日下午12:02:21
 *
 */
public class Cream extends CondimentDecoratore {

	
	/**
	 * 飲料
	 */
	private Beverage beverage;
	
	/**
	 * 構造函數,初始化奶油的裝飾在一杯飲料之上
	 * 
	 * @param beverage
	 */
	public Cream(Beverage beverage, BigDecimal price){
		this.beverage 	= beverage;
		this.price 		= price;
	}
	
	@Override
	public String getDescription() {
		// TODO Auto-generated method stub
		return beverage.getDescrition() + " + cream";
	}

	@Override
	public BigDecimal cost() {
		// TODO Auto-generated method stub
		return this.price.add(this.beverage.cost());
	}

}

 

好,那如今購買一杯濃縮咖啡,還要來點摩卡,來點豆漿,系統是如何實現的吧!

package cn.org.lennon.decorate;

import java.math.BigDecimal;

import cn.org.lennon.decorate.condiment.Mocha;
import cn.org.lennon.decorate.condiment.SoybeanMilk;
import cn.org.lennon.decorate.mian.Espresso;

/**
 * 主程序
 * 
 * @author lennon
 * @time 2017年3月19日下午12:14:06
 *
 */
public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		// 我要購買一杯又豆漿+摩卡的濃縮咖啡
		Beverage bev = new SoybeanMilk( new Mocha(new Espresso( new BigDecimal(11)),  new BigDecimal(15)), new BigDecimal(10));
		
		System.out.println("我在星巴茲的第一杯咖啡的消費是:" + bev.cost());
		
	}

}

總結

    站在裝飾者模式的角度去思考星巴茲的故事,咱們能夠看出一個良好系統的設計,就像一門偉大的藝術。裝飾者模式幾乎完美的遵循了軟件開發原則中的「開閉原則」。整個系統的業務拓展只要實現Beverage與CondimentDecorator就能夠。同時,裝飾者模式也是一個很好的「組合優先考慮於繼承」的完美案例。由於裝飾者模式正好完美契合了星巴茲趕上的問題,可是在實際的軟件開發中,咱們經常不能很是完整的照搬模式,而且在真正的使用設計模式的時候,必定要考慮好防止過分設計,從而增長軟件複雜度。

    裝飾者模式從代碼來看是很是 簡單的,經過站在代碼的角度來分析從新分析星巴茲的訂單系統的故事,而後再從新回來思考裝飾者模式,必定可讓你有另外一番收穫!

 

歡迎指教,我是大天然的搬運工!

相關文章
相關標籤/搜索