背景:
买咖啡的订单系统,所有的饮品都要继承Beverage抽象类,在购买饮品时也可以加入其他配料(如豆浆、摩卡、奶泡等)。
方案一:在Beverage中添加配料的布尔值,并通过set、get方法获取,利用cost方法计算总体价格。
不可行:如果要添加新配料的话或者改价格需要修改原先代码,且没办法买双倍的配料(用int计算调料数)
设计:
原则一:类应该对扩展开发,对修改关闭。
实现一:尽量多的使用组合而少用继承,将饮品视为一个对象,然后用配料去装饰他。
优点:之前学过,利用组合和委托可以在运行时具有继承行为的效果。利用继承设计子类行为是在编译时静态决定的, 而且所有的子类都会集成到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。通过动态地组合对象可以写信的代码添加新的功能而无需修改现有代码。尽量少的修改现有代码可以大幅度减少Bug。
实现:
// 实现一个基本的Beverage抽象类,也可以用接口,但是题目背景给的是抽象类
// 饮料和配料都需要继承Beverage,以便达到类型匹配
public abstract class Beverage {
String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
// 实现配料抽象类
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription(); // 所有配料装饰者都必须重新实现getDescription()方法
}
// 实现Espresso饮料
public class Espresso extends Beverage {
public Espresso() {
description = "Espresso";
}
@Override
public double cost() {
return 1.99;
}
}
// 实现配料
public class Mocha extends CondimentDecorator {
Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
// 利用委托的做法,得到一个叙述,然后再其后增加叙述
@Override
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
@Override
public double cost() {
return 0.20 + beverage.cost();
}
}
// 订单系统
public class StarbuzzCoffee {
public static void main(String[] args) {
Beverage beverage = new Espresso(); // 定一杯Espresso
beverage = new Mocha(beverage); // 加配料Mocha
System.out.println(beverage.getDescription() + " $" + beverage.cost());
}
}
输出:Espresso, Mocha $2.19
Java I/O也是装饰者,InputStream是抽象组件,FileInputStream、StringBufferInputStream、ByteArrayInputStream等是一个可以被装饰着包装起来地具体组件(类似本文的Espresso类),FilterInputStream是一个抽象装饰者(类似本文的CondimentDecorator抽象类),PushbackInputStream、BufferedInputStream、DataInputStream、Line Number InputStream是一个具体的装饰者实现(类似本文的Mocha)
总结:
装饰者模式:动态地将责任附加到对象上。想要扩展功能,装饰着提供有别于继承地另一种选择。
装饰者和被装饰者必须是一样的类型,也就是需要拥有共同的超类,这是关键。我们利用继承达到“类型匹配”,而不是利用继承获取“行为”,并不违背我们“少继承,多组合”的原则。
装饰者模式意味着一群装饰者类,这些类可以用来包装具体组件。装饰者类反映出被装饰的组件类型,装饰者可以在被装饰者的行为前面与/或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的。
但是装饰者会导致设计终出现需对小对象,如果过度使用,会让程序变得很复杂,所以后面我们会采用工厂模式(Factory)和生成器模式(Builder)。