《Head First Design Patterns》:第3章,装饰器(Decorator)模式

装饰器模式的定义

装饰器模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

如下:

  1. ConcreteComponent是我们将要动态地加上新行为的对象,它扩展自Component
  2. 每个装饰者Decorator都有一个(包装一个)组件对象,也就是说,装饰者有一个实例变量保存某个Component是的引用;
  3. 每个具体的装饰者可以加上新的方法。新行为是通过在旧行为前面或后面做一些计算来添加的;

场景

星巴兹咖啡店的订单系统

场景介绍

星巴兹咖啡店扩展迅速,他们准备更新订单系统,以合乎他们的饮料供应要求。类设计原来是这样的:a)饮料类Beverage是一个抽象类,店内所有提供的饮料都要继承此类;b)cost()方法是抽象的,子类必须定义自己的实现,以实现对每种饮料计算价格;c)购买咖啡时,也可以加入各种调料,例如:蒸奶、豆浆、摩卡或覆盖奶泡;星巴兹会根据所加入的调料收取不同的费用。

采用这样的设计,他们的类将会爆炸:

解决方案

这里采用的做法是:以饮料为主体,在运行时以调料来“装饰”饮料。比如,如果顾客想要摩卡和奶泡深焙咖啡,那么需要这样做:

  1. 拿一个深焙咖啡DarkRoast对象;
  2. 以摩卡Mocha对象装饰它;
  3. 以奶泡Whip对象再进行装饰它;
  4. 调用cost()方法,并依赖委托将调料的价钱加上去;
1
2
3
4
5
6
7
8
9
public abstract class Beverage {
String description = "Unknown Beverage";

public String getDescription() {
return description;
}

public abstract double cost();
}

实现一些饮料,我们先实现浓缩咖啡Espresso和综合咖啡HouseBlend

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Espresso extends Beverage {
public Espresso() {
description = "Espresso";
}

public double cost() {
return 1.99;
}
}

public class HouseBlend extends Beverage {
public HouseBlend() {
description = "House Blend Coffee";
}

public double cost() {
return .89;
}
}

调料抽象类:

1
2
3
4
5
public abstract class CondimentDecorator extends Beverage {
Beverage beverage;

public abstract String getDescription();
}

实现摩卡口味的咖啡:a)getDescription()中加上摩卡的描述,b)cost()如中加上了摩卡的费用,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
this.beverage = beverage;
}

public String getDescription() {
return beverage.getDescription() + ", Mocha";
}

public double cost() {
return .20 + beverage.cost();
}
}

我们来使用一下新的订单系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class StarbuzzCoffee {
public static void main(String args[]) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription()
+ " $" + beverage.cost());

Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription()
+ " $" + beverage2.cost());

Beverage beverage3 = new HouseBlend();
beverage3 = new Soy(beverage3);
beverage3 = new Mocha(beverage3);
beverage3 = new Whip(beverage3);
System.out.println(beverage3.getDescription()
+ " $" + beverage3.cost());
}
}

Java中的装饰者

Java I/O

  1. InputStream是抽象组件;
  2. FileInputStreamStringBufferInputStreamByteArrayInputStream是可以被装饰者包起来的具体组件,还有少数类没有被列出,如ObjectInputStream;
  3. PushbackInputStreamBufferedInputStreamDataInputStreamInflatorInputStreamZipInputStream是具体的装饰者;
  4. Java I/O也引出装饰者模式的一个“缺点”:利用装饰者模式,常常造成设计中大量的小类,数量实在太多,可能会造成使用此API的程序员困扰

实现输入流中所有大写字符转成小写的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class LowerCaseInputStream extends FilterInputStream {
public LowerCaseInputStream(InputStream in) {
super(in);
}

public int read() throws IOException {
int c = in.read();
return (c == -1 ? c : Character.toLowerCase((char) c));
}

public int read(byte[] b, int offset, int len) throws IOException {
int result = in.read(b, offset, len);
for (int i = offset; i < offset + result; i++) {
b[i] = (byte) Character.toLowerCase((char) b[i]);
}
return result;
}
}

参考

  1. https://wickedlysmart.com/head-first-design-patterns/