设计模式之装饰者模式

设计模式之装饰者模式

定义装饰者模式

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

设计原则

类应该对扩展开发,对修改关闭

装饰者模式类图

用装饰者来设计我们的饮料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 饮料抽象类
* @author Mr.zxb
* @date 2020-08-13 20:48:22
*/
public abstract class Beverage {
protected String description = "Unknown Beverage";

/**
* 获取饮料描述
* @return
*/
public String getDescription() {
return description;
}

/**
* 饮料成本抽象方法
* @return
*/
public abstract double cost();

}
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 调味品装饰器,继承自饮料抽象类
* @author Mr.zxb
* @date 2020-08-13 20:50:47
*/
public abstract class CondimentDecorator extends Beverage {
/**
* 获取调味品描述
* @return
*/
@Override
public abstract String getDescription();
}

设计饮料的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 浓咖啡
* @author Mr.zxb
* @date 2020-08-13 20:52:53
*/
public class Espresso extends Beverage {
public Espresso() {
// 浓咖啡描述
super.description = "Espresso";
}

/**
* 成本
* @return
*/
@Override
public double cost() {
return 1.99;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @author Mr.zxb
* @date 2020-08-13 20:54:36
*/
public class HouseBlend extends Beverage {
public HouseBlend() {
super.description = "House Blend Coffee";
}

@Override
public double cost() {
return 0.89;
}
}

设计调料的对象

我们已经完成了抽象组件(Beverage),有了具体组件(HouseBlend),也有了抽象装饰者(CondimentDecorator)。现在就来实现具体装饰者。先从摩卡下手:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 摩卡咖啡
* @author Mr.zxb
* @date 2020-08-13 20:56:58
*/
public class Mocha extends CondimentDecorator {

/**
* 1) 用一个实例变量记录饮料,也就是被装饰者。
* 2) 想办法让装饰者(饮料)被记录到实例变量中。
*/
private final Beverage beverage;

public Mocha(Beverage beverage) {
this.beverage = beverage;
}

@Override
public String getDescription() {
// 获取完整的调料描述
return beverage.getDescription() + ", Mocha";
}

@Override
public double cost() {
// 首先把调用委托给被装饰对象,以计算价格,然后加上 Mocha的价格,得到最后的结果
return 0.20 + beverage.cost();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
*
* @author Mr.zxb
* @date 2020-08-13 21:26:54
*/
public class Whip extends CondimentDecorator {

private final Beverage beverage;

public Whip(Beverage beverage) {
this.beverage = beverage;
}

@Override
public double cost() {
return 0.5 + beverage.cost();
}

@Override
public String getDescription() {
return beverage.getDescription() +", Whip";
}
}

测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 测试类
* @author Mr.zxb
* @date 2020-08-13 21:20:31
*/
public class StarbuzzCoffee {
public static void main(String[] args) {
// 创建一个 Espress,不需要调料,打印价格和描述
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + " $" + beverage.cost());

// 创建一各 DarkRost对象
Beverage beverage2 = new DarkRoast();
// 用Mocha 装饰它
beverage2 = new Mocha(beverage2);
// 用第二个Mocha 装饰它
beverage2 = new Mocha(beverage2);
// 用Whip 装饰它
beverage2 = new Whip(beverage2);
// 输出价格和描述
System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
}
}

来看看结果

1
2
Espresso $1.99
Dark Roast, Mocha, Mocha, Whip $1.79

让我们来看看Java I/O里的装饰者

装饰的 java.io 类

编写自己的Java I/O 装饰者

编写一个装饰者,把输入流内的所有大写字符转成小写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.design.decorator.io;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
* 将大写转成小写的装饰者对象
* @author Mr.zxb
* @date 2020-08-13 22:05:23
*/
public class LowerCaseInputStream extends FilterInputStream {

/**
* Creates a <code>FilterInputStream</code>
* by assigning the argument <code>in</code>
* to the field <code>this.in</code> so as
* to remember it for later use.
*
* @param in the underlying input stream, or <code>null</code> if
* this instance is to be created without an underlying stream.
*/
protected LowerCaseInputStream(InputStream in) {
super(in);
}

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

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

测试我们新的Java I/O装饰者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 测试新的Java I/O 装饰者
* @author Mr.zxb
* @date 2020-08-13 22:10:31
*/
public class InputTest {
public static void main(String[] args) throws IOException {
InputStream in = null;
try {
// 设置输入流 InputStream,先用 BufferedInputStream装饰它,再用我们新的 LowerCaseInputStream过滤器装饰
in = new LowerCaseInputStream(new BufferedInputStream(System.in));
int c;
while ((c = in.read()) > 0) {
System.out.print((char) c);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
in.close();
}
}
}
}
1
2
3
4
5
6
7
运行结果:
HELLO
hello
HI
hi
HELLO WORLD
hello world

总结

  • 继承属于扩展形式之一,但不见得达到弹性设计的最佳方式
  • 在我们的设计中,应该允许行为可以被扩展,而无须修改现有的代码
  • 组合和委托可用于在运行时动态地加上新的行为
  • 除了继承,装饰者模式也可以让我们扩展行为
  • 装饰者模式意味着一群装饰者类,这些类用来包装具体组件
  • 装饰者类反映出被装饰的组件类型
  • 装饰者可以在被修饰者的行为前面与/或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的
  • 你可以用无所个装饰者包装一个组件
  • 装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型
  • 装饰者会导致设计中出现许多小装饰对象,如果过度使用,会让程序变得很复杂