本章能够成为 “给爱用继承的人一个全新的设计眼界”。咱们即将再度探讨继承滥用的问题。并在会在本章中学到如何使用对象组合的方式,作到运行时装饰类。为什么?一旦你熟悉了装饰的技巧。你将可以在不修改任何底层代码的状况下,给你的(或别人的)对象赋予新的职责。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就能够。同时,装饰者模式也是一个很好的“组合优先考虑于继承”的完美案例。由于装饰者模式正好完美契合了星巴兹赶上的问题,可是在实际的软件开发中,咱们经常不能很是完整的照搬模式,而且在真正的使用设计模式的时候,必定要考虑好防止过分设计,从而增长软件复杂度。
装饰者模式从代码来看是很是 简单的,经过站在代码的角度来分析从新分析星巴兹的订单系统的故事,而后再从新回来思考装饰者模式,必定可让你有另外一番收获!
欢迎指教,我是大天然的搬运工!