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就能够。同时,装饰者模式也是一个很好的“组合优先考虑于继承”的完美案例。由于装饰者模式正好完美契合了星巴兹赶上的问题,可是在实际的软件开发中,咱们经常不能很是完整的照搬模式,而且在真正的使用设计模式的时候,必定要考虑好防止过分设计,从而增长软件复杂度。

    装饰者模式从代码来看是很是 简单的,经过站在代码的角度来分析从新分析星巴兹的订单系统的故事,而后再从新回来思考装饰者模式,必定可让你有另外一番收获!

 

欢迎指教,我是大天然的搬运工!

相关文章
相关标签/搜索