Sui

设计模式-装饰者模式

字数统计: 1.4k阅读时长: 5 min
2021/02/23 Share

设计模式-装饰者模式

JDK中,使用到了很多设计模式,例如装饰者模式、观察者模式,它广泛存在于Java I/O中,说实话,刚学习Java I/O时有点懵,他的类是在太多了,可供选择的就十多种,还有很多叫不上名字的IO类,当时一度怀疑自己不是学编程的选手。

今天重点所说的就是装饰者模式,也算是一个总结吧

场景

这个场景很简单,为某冷饮店制造一个收费系统。

他的业务是这样的,主要的为饮品收费,其他的收费项目为配料收费,例如在点了饮品的基础上增加了珍珠、椰果等配料,然后计算总的费用。

问题解决

大家的想法可能是这样:当购买哪一种类的饮料时,就创建出来这个对象,如果想另外增加配料,再创建出来配料类的对象,最后计算出价格。但是怎么实现呢,要讲弹性,以方便日后的扩展

最初可以设计出一个抽象类,将饮料抽象出来

1
2
3
4
5
6
7
public abstract class Beverage {
public String getDescription() {
return "unknown beverage";
}

public abstract float cost();
}

接下来就是围绕这个类展开,从而引出装饰者模式。在往下进行之前,先说一个设计原则:

对修改关闭,对扩展开放(开放封闭原则)

新创建的饮料可以完全继承自Beverage类,并且实现其中的cost()和覆盖getDescription(),我们创建两个饮料类拿铁(Latte)和卡布奇诺(Cappuccino)

1
2
3
4
5
6
7
8
9
10
11
public class Latte extends Beverage{
@Override
public float cost() {
return 12.1f;
}

@Override
public String getDescription() {
return "Latte";
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Cappuccino extends Beverage{
@Override
public float cost() {
return 7.21f;
}

@Override
public String getDescription() {
return "Cappuccino";
}
}

在装饰者模式中,这两类被称为被装饰者,而这个场景中,真正的装饰者是调料,我们看一下调料的抽象类

1
2
3
4
5
6
public abstract class Flavour extends Beverage {
@Override
public String getDescription() {
return "Flavour";
}
}

另外再去实现两种调料珍珠(Pearl)、椰肉(Coconut)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Pearl extends Flavour{

private Beverage beverage;

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

@Override
public float cost() {
return beverage.cost() + 2.1f;
}

@Override
public String getDescription() {
return beverage.getDescription() + " Pearl";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Coconut extends Flavour{

private Beverage beverage;

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

@Override
public float cost() {
return beverage.cost() + 4.2f;
}

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

目前我们就完成了所有所需的类,UML类图如下

其中PearlCoconut装饰者类,负责装饰LatteCappuccino,且装饰者与被装饰者应该有相同的类型,即Beverage

如果我想点一份Latte,并且加上Pearl配料,应该如何计算金钱呢

1
2
3
4
5
6
7
8
9
10
public class Consumption {
public static void main(String[] args) {
Latte latte = new Latte();

Pearl pearl = new Pearl(latte);

System.out.println("total cost: " + pearl.cost());
System.out.println(pearl.getDescription());
}
}

最后打印出

1
2
total cost: 14.200001
Latte Pearl

这样的扩展是否很灵活呢,通过装饰者对被装饰者的装饰,在不修改原有代码的前提下,完成了如此具有弹性的功能。

此时我想点一份Cappuccino加两份Coconut

1
2
3
4
5
6
7
8
9
10
11
public class Consumption {
public static void main(String[] args) {
Cappuccino cappuccino = new Cappuccino();

Coconut coconut = new Coconut(cappuccino);
coconut = new Coconut(coconut);

System.out.println("total cost: " + coconut.cost());
System.out.println(coconut.getDescription());
}
}

输出

1
2
total cost: 15.61
Cappuccino Coconut Coconut

I/O类中的装饰者模式

以输入流举例(输出流也是同理),InputStream可以看成是抽象的组件,FilterInputStream可以看成是抽象装饰者,而她下面的类就可以看成是具体的装饰者,他们都是对以及InputStream的装饰

既然说的这么细了,那就进到FilterInputStream的源码中看一看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class FilterInputStream extends InputStream {
// 被装饰的对象
protected volatile InputStream in;

protected FilterInputStream(InputStream in) {
this.in = in;
}

public int read() throws IOException {
// 调用被装饰的对象方法
return in.read();
}

// 部分省略
}

他和咱们上面饮品的例子结构差不多吧

Java I/O也引出来了装饰者模式的一个缺点,利用装饰者模式,常常造成设计中有大量的小类,数量实在太多,可能会给使用此API的程序员造成困扰。

我们在知道了Java I/O类的原理后,可以自己动手写一个Java I/O类的API,功能是将文件中的全部大写转换为小写

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
public class LowerFileInputStream extends FileInputStream {

private FileInputStream fileInputStream;

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

@Override
public int read(byte[] b) throws IOException {
int read = super.read(b);

for (int i = 0; i < read; i++) {
b[i] = (byte) Character.toLowerCase((char) b[i]);
}

return read;
}

public LowerFileInputStream(String name) throws FileNotFoundException {
super(name);
fileInputStream = new FileInputStream(name);
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) throws Exception {
LowerFileInputStream lowerFileInputStream = new LowerFileInputStream("lower.txt");

int read = 0;

StringBuilder stringBuilder = new StringBuilder();

while((read = lowerFileInputStream.read()) != -1) {
stringBuilder.append((char)read);
}

lowerFileInputStream.close();

System.out.println(stringBuilder.toString());
}

输出

1
2
3
4
5
# 原始文件内容
I am Programmer

# 输出内容
i am programmer

总结

本篇文章以某饮料公司的消费系统为场景,引出了装饰者模式,并且得到了一个重要的设计原则:

类只对扩展开放,对修改关闭(开放封闭原则)

还总结出Java IO中存在着大部分的装饰者模式,同时也指出了装饰者模式的不足之处。

最后自己动手实现了一个依靠装饰者模式自定义的IO类。

CATALOG
  1. 1. 设计模式-装饰者模式
    1. 1.1. 场景
    2. 1.2. 问题解决
    3. 1.3. I/O类中的装饰者模式
    4. 1.4. 总结