今天咱们来学习一下装饰者模式。做为一名程序猿,相信许多人都跟我同样,在平时写代码的过程当中,习惯使用继承。可是继承有时候会出现很是严重的问题,不过,没担忧。装饰者模式将会给爱用继承的咱们一个全新的设计眼界!java
1、星巴兹咖啡的故事ide
咱们经过一个生动有趣的例子来引出咱们今天的主角--装饰者模式。学习
一、如今呢,有一个咖啡馆,它有一套本身的订单系统,当顾客来咖啡馆的时候,能够经过订单系统来点本身想要的咖啡。他们原先的设计是这样子的:ui
二、此时、咖啡馆为了吸引更多的顾客,须要在订单系统中容许顾客选择加入不一样调料的咖啡,例如:蒸奶(Steamed Milk)、豆浆(Soy)、摩卡(Mocha,也就是巧克力风味)或覆盖奶泡。星巴兹会根据所加入的调料收取不一样的费用。因此订单系统必须考虑到这些调料部分。this
下面是他们的第一次尝试:spa
这种设计确定是不行的,简直分分钟把人逼疯的节奏,有木有!设计
三、这时,有我的提出了新的方案,利用实例变量和继承,来追踪这些调料。code
具体为:先从Beverage基类下手,加上实例变量表明是否加上调料(牛奶、豆浆、摩卡、奶泡……),对象
这种设计虽然知足了如今的需求,可是咱们想一下,若是出现下面状况,咱们怎么办,blog
①、调料价钱的改变会使咱们更改现有代码。
②、一旦出现新的调料,咱们就须要加上新的方法,并改变超类中的cost()方法。
③、之后可能会开发出新饮料。对这些饮料而言(例如:冰茶),某些调料可能并不适合,可是在这个设计方式中,Tea(茶)子类仍将继承那些不适合的方法,例如:hasWhip()(加奶泡)。
④、万一顾客想要双倍摩卡咖啡,怎么办?
很明显,上面的设计并不可以从根本上解决咱们所碰到的问题。而且这种设计违反了 开放关闭原则(类应该对扩展开放,对修改关闭。)。
那咱们怎么办呢?好啦,装饰者能够很是完美的解决以上的全部问题,让咱们有一个设计很是nice的咖啡馆。
2、定义
装饰者模式:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
3、实现
下面咱们就利用装饰者模式来实现一个全新的咖啡馆。
一、咱们要以饮料为主体,而后在运行时以调料来“装饰”(decorate)饮料。比方说,若是顾客想要摩卡和奶泡深焙咖啡,那么,要作的是:
①、拿一个深焙咖啡(DarkRoast)对象
②、以摩卡(Mocha)对象装饰它
③、以奶泡(Whip)对象装饰它
④、调用cost()方法,并依赖委托(delegate)将调料的价钱加上去。
好了!可是如何“装饰”一个对象,而“委托”又要如何与此搭配使用呢?那就是把装饰者对象当成“包装者”。让咱们看看这是如何工做的:
二、设计
咱们将咱们所知道的写下来:
①、装饰者和被装饰对象有相同的超类型。
②、你能够用一个或多个装饰者包装一个对象。
③、既然装饰者和被装饰对象有相同的超类型,因此在任何须要原始对象(被包装的)的场合,能够用装饰过的对象代替它。
④、装饰者能够在所委托被装饰者的行为以前与 / 或以后,加上本身的行为,以达到特定的目的。
⑤、对象能够在任什么时候候被装饰,因此能够在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。
下面,咱们来看一下装饰者模式的类图:
利用装饰者模式来实现咱们的订单系统的类图:
咱们已经将订单系统设计完毕,下面让咱们用代码来实现它吧!
三、代码实现:
饮料抽象类:
/** * @author fan_rc@suixingpay.com * @description 饮料抽象类 * @date 2019/9/17 20:53 */ public abstract class Beverage { String description = "Unknown Beverage"; public String getDescription() { return description; } /** * cost方法是用来返回饮料的价钱(需在具体类中本身实现) * * @return */ public abstract BigDecimal cost(); }
深焙咖啡类:
/** * 深焙咖啡类(一种具体的饮料) */ public class DarkRoast extends Beverage { /** * 说明他是DarkRoast饮料 */ public DarkRoast() { description = "DarkRoast"; } /** * 实现cost方法,用来返回DarkRoast(深焙咖啡)的价格 * * @return */ @Override public BigDecimal cost() { return new BigDecimal("3.00"); } }
低咖啡因咖啡类:
/** * 低咖啡因咖啡类(一种具体的饮料) */ public class Decaf extends Beverage { /** * 说明他是Decaf饮料 */ public Decaf() { description = "Decaf"; } /** * 实现cost方法,用来返回Decaf(低咖啡因咖啡)的价格 * * @return */ @Override public BigDecimal cost() { return new BigDecimal("4.00"); } }
浓缩咖啡类:
/** * 浓缩咖啡类(一种具体饮料) */ public class Espresso extends Beverage { /** * 说明他是Espresso饮料 */ public Espresso() { description = "Espresso"; } /** * 实现cost方法,用来返回Espresso(浓缩咖啡)的价格 * * @return */ @Override public BigDecimal cost() { return new BigDecimal("2.00"); } }
调料装饰着抽象类:
/** * @author fan_rc@suixingpay.com * @description 调料装饰着抽象类(继承自饮料抽象类) * @date 2019/9/17 20:56 */ public abstract class CondimentDecorator extends Beverage { /** * 全部的调料装饰者都必须从新实现getDescription()方法 * 这样才可以用递归的方式来获得所选饮料的总体描述 * * @return */ public abstract String getDescription(); }
摩卡调料类:
/** * 摩卡调料类(继承自CondimentDecorator) */ public class Mocha extends CondimentDecorator { /** * 用一个实例变量记录饮料,也就是被装饰者 */ Beverage beverage; /** * 构造器初始化饮料变量 * * @param beverage */ public Mocha(Beverage beverage) { this.beverage = beverage; } /** * 在原来饮料的基础上添加上Mocha描述(原来的饮料加入Mocha调料,被Mocha调料装饰) * * @return */ @Override public String getDescription() { return beverage.getDescription() + ",Mocha"; } /** * 在原来饮料的基础上加上Mocha的价格(原来的饮料加入Mocha调料,被Mocha调料装饰) * * @return */ @Override public BigDecimal cost() { return new BigDecimal("0.2").add(beverage.cost()); } }
豆浆调料类:
/** * 豆浆调料类(继承自CondimentDecorator)) */ public class Soy extends CondimentDecorator { /** * 用一个实例变量记录饮料,也就是被装饰者 */ Beverage beverage; /** * 构造器初始化饮料变量 * * @param beverage */ public Soy(Beverage beverage) { this.beverage = beverage; } /** * 在原来饮料的基础上添加上Soy描述(原来的饮料加入Soy调料,被Soy调料装饰) * * @return */ @Override public String getDescription() { return beverage.getDescription() + ",Soy"; } /** * 在原来饮料的基础上加上Soy的价格(原来的饮料加入Soy调料,被Soy调料装饰) * * @return */ @Override public BigDecimal cost() { return new BigDecimal("0.3").add(beverage.cost()); } }
奶泡调料类:
/** * 奶泡调料类(继承自CondimentDecorator) */ public class Whip extends CondimentDecorator { /** * 用一个实例变量记录饮料,也就是被装饰者 */ Beverage beverage; /** * 构造器初始化饮料变量 * * @param beverage */ public Whip(Beverage beverage) { this.beverage = beverage; } /** * 在原来饮料的基础上添加上Whip描述(原来的饮料加入Whip调料,被Whip调料装饰) * * @return */ @Override public String getDescription() { return beverage.getDescription() + ",Whip"; } /** * 在原来饮料的基础上加上Whip的价格(原来的饮料加入Whip调料,被Whip调料装饰) * * @return */ @Override public BigDecimal cost() { return new BigDecimal("0.4").add(beverage.cost()); } }
咖啡馆(模拟顾客下单):
/** * 咖啡馆(供应咖啡) */ 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()); } }
咱们能够看到运行结果:
从以上,咱们能够知道,当咱们使用继承,致使子类膨胀,咱们不想增长不少子类的状况下,将具体功能职责划分,同时继承装饰者超类,动态地给一个对象添加一些额外的职责便实现了咱们的装饰者模式。
4、优缺点
一、优势:装饰类和被装饰类能够独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式能够动态扩展一个实现类的功能。
二、缺点:多层装饰比较复杂。
5、使用场景:
一、扩展一个类的功能。
二、动态增长功能,动态撤销。
实际使用:这里咱们说一下,在java中I/O便使用了装饰者模式。
6、装饰者用到的设计原则:
一、多用组合,少用继承。
二、对扩展开放,对修改关闭。