Java 之 装饰者模式

本章可以成为 “给爱用继承的人一个全新的设计眼界”。我们即将再度探讨继承滥用的问题。并在会在本章中学到如何使用对象组合的方式,做到运行时装饰类。为何?一旦你熟悉了装饰的技巧。你将能够在不修改任何底层代码的情况下,给你的(或别人的)对象赋予新的职责。

章节开头,我们看到了一个屌丝大叔端着一杯星巴兹的咖啡,一脸贱样的想着,“曾经我以为男子汉应该用继承梳理一切,后来我领教到运行时扩展,远比编译时期的继承威力大。”

1、笨方法

小子的学习方式是:先把星巴兹的问题看懂(业务难点),然后把章节拉到后边,着手把代码敲了一遍,这个时候回头再来思考星巴兹遇到的业务难点,自然也就了然于胸。如果此时的你茫然无措,对这设计模式这本书有些无可奈何,不妨尝试一下我这种笨拙的办法?

2、星巴兹故事

好,大家都对于星巴兹这家咖啡店非常熟悉吧?我今天在“得到”这个APP上阅读了吴军老师讲的品味咖啡,倒是对于咖啡有了一点点认知,至少不会轻易觉得咖啡只是用来提神的苦水。如果你也想有一点点了解,不妨看看我的读书笔记:http://www.jianshu.com/p/f9aa11449ab8(如何可以,建议在得到这个APP上订阅吴军老师的专栏吧,尝试着另一种生活的态度)

 

那好,说正题,我们说一下星巴兹在中国迅速铺开市场的时候,想要更新订单系统时遇上的业务痛点:星巴兹目前有四种饮料,他们单独售价都是固定好的,而此时因为业务拓展,为了迎合市场客户的需求,新增了几种调料,后续可能会增加其他的饮料种类也说不定哦。那么,基于原有的订单业务是怎么实现的呢?

先来看原先的订单系统的饮料类图:

132626_BWSz_1589819.png

所以如果盲目的按照原先的继承结构来实现,那么星巴兹可能需要实现的类图如下:

132846_e2Qb_1589819.png

这已经不是简单的类爆炸了,如果接下来星巴兹在中国的业务持续飙升呢?整个订单系统将庞大到无法想象,已经可以预料到,如果星巴兹的工程师不被逼疯,大概也只能跑路了。因为维护近乎无法维护的代码,足矣让工程师逃之夭夭。

或许有沉迷继承的家伙会站出来,指着小子的鼻子说,只要在继承的结构上优化,可以解决这种问题:比如在Beverage基类上做处理,就可以完美解决庞大类的问题,而且仅仅只需要五个类,不信你看看设计好的类图:

133353_gHQo_1589819.png

乍一看,诶,似乎真的可以完美解决的耶?但是仔细想想,这个继承的结构是站在什么角度上来考虑的?是站在既定的业务不再扩展而设计的!比如此时要新增一种调料,是不是还要继续修改基类?比如有一个客户的口味非常重,他需要两个分量的摩卡,系统怎么实现呢?并且,假如星巴兹调查到竞争对手在买的一款绿茶在中国非常的畅销,客户正在源源不断的流失,总部要求星巴兹分部一定要加上绿茶,但是绿茶的结构里,不应该有摩卡,不应该有豆浆啊!所以,这个结构的实现并未从真正意义上解决系统痛点。如果有其他的设计方案,我们是否先考虑考虑软件开发的系统设计原则?比如:

软件开发的无上法典:开闭原则

关于开闭原则,这里不加以赘述,如果不了解的童鞋,请猛烈戳下边的链接:http://blog.csdn.net/zhengzhb/article/details/7296944(不是小子的笔记,但是写的很好,双手奉上)

 

既然继承不能很好的解决问题,思考更优的解决方案之后,请跟小子一起认识一下今天的主题——装饰者模式。

前边说道星巴兹的订单系统扩展的问题,简直让人无语,闻风丧胆,那么如果使用装饰者模式是如何解决问题的呢?很简单,思考——“把星巴兹提供的饮料(请自行过滤掉配料)作为主体对象(被装饰对象),然后把配料(装饰对象)一步步装配到饮料的主体对象上。”

比如: 我到了星巴兹咖啡店,点了一杯浓缩咖啡Espresso ,这个时候我想要加一些摩卡Mocha,再加一些豆浆 SoybeanMilk。所以这杯咖啡的价格应该是:Espresso + Mocha + SoybeanMilk。从一个主体Espresso装饰上Mocha,再装饰上SoybeanMilk,如下图:

135708_eBRJ_1589819.png

  那么价格如何计算?

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

3、代码实现

装饰者模式定义:动态的将责任附加到对象上,若要扩展功能,装饰者模式提供了比继承更加弹性更加优越的替代方案;

另一个软件开发无上法典:组合优先考虑于继承。

  以下是实现的代码,注意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就可以。同时,装饰者模式也是一个很好的“组合优先考虑于继承”的完美案例。因为装饰者模式正好完美契合了星巴兹遇上的问题,但是在实际的软件开发中,我们常常不能非常完整的照搬模式,并且在真正的使用设计模式的时候,一定要考虑好防止过度设计,从而增加软件复杂度。

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

 

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

 

 

 

 

 

 

 

 

 

 

 

 

 

转载于:https://my.oschina.net/u/1589819/blog/862496

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值