装饰者模式

装饰者模式是继承的一种替代方案。它以对客户端透明的状态动态的为对象赋予不同的职责,而不用进行大量子类的扩写。

模式结构

它的类图如下:

3985563a0d0ac0c5bdf5c93.png

Component(抽象构件): 通常作为接口被实现,用于规范被装饰角色
ConcreteComponent(具体构件): 作为揽收职责的对象类,实现了抽象构件接口
Decorator(装饰职责超类): 用于规范具体装饰组件,持有一个具体构建实例
ConcreteDecoratorA,B(具体装饰类): 作为具体装饰职责,为对象扩展功能

装饰者模式实例

记录这篇笔记主要目的是把io类啃下来,单纯是理论就太枯燥了。在对io类三拜五扣之前先引入一段栗子

考虑一下咖啡厅的业务,咖啡作为产出,但不同的人口味不尽相同,有的喜欢加薄荷有的喜欢加牛奶;不同的配料需要收不同的钱。当我们使用继承来进行抽象,可能会多出许多coffee的子类,例如MintCoffee,MilkCoffee….这样的实现是不优雅的,考虑客人可能要加一点薄荷,再加一点牛奶。我们总不能上两杯吧

这个时候就需要装饰者模式派上用场了,定义出抽象构件 CoffeeComponent

1
2
3
4
5
6
7
8
9
10
package com.meteor;

public interface CoffeeComponent {
//返回咖啡的名称
String getName();
//返回咖啡的配料
String getAdd();
//返回咖啡的价格
double getPrice();
}

以及具体构件,客人可能需要一杯蓝山咖啡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.meteor;

public class BlueCoffee implements CoffeeComponent{
@Override
public String getName() {
return "蓝山咖啡";
}

@Override
public String getAdd() {
return "";
}

@Override
public double getPrice() {
return 5.0;
}
}

装饰职责超类:

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
package com.meteor;

public class CoffeeDecorator implements CoffeeComponent{

private CoffeeComponent coffeeEntity;

public CoffeeDecorator(CoffeeComponent coffeeEntity){
this.coffeeEntity = coffeeEntity;
}

@Override
public String getName() {
return coffeeEntity.getName();
}

@Override
public String getAdd() {
return coffeeEntity.getAdd();
}

@Override
public double getPrice() {
return coffeeEntity.getPrice();
}

public CoffeeComponent getCoffeeEntity() {
return coffeeEntity;
}
}

根据客人的喜好写出具体装饰类

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
package com.meteor.decorator;

import com.meteor.CoffeeComponent;
import com.meteor.CoffeeDecorator;

public class MilkCoffeeDecorator extends CoffeeDecorator {

public MilkCoffeeDecorator(CoffeeComponent coffeeEntity) {
super(coffeeEntity);
}

@Override
public String getName() {
return getCoffeeEntity().getName();
}

@Override
public double getPrice() {
return getCoffeeEntity().getPrice()+2.0;
}

@Override
public String getAdd() {
return getCoffeeEntity().getAdd()+"牛奶 ";
}
}
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
package com.meteor.decorator;

import com.meteor.CoffeeComponent;
import com.meteor.CoffeeDecorator;

public class MintCoffeeDecorator extends CoffeeDecorator {


public MintCoffeeDecorator(CoffeeComponent coffeeEntity) {
super(coffeeEntity);
}

@Override
public String getName() {
return getCoffeeEntity().getName();
}

@Override
public String getAdd() {
return getCoffeeEntity().getAdd()+"薄荷 ";
}

@Override
public double getPrice() {
return getCoffeeEntity().getPrice()+2.0;
}
}

到这里就完成了,当需要推出新口味的时候你可以再次增加装饰类。在客人需要薄荷的时候赋予薄荷的职责,或者是直接new 一杯红豆牛奶咖啡….不论是什么口味,他总是一杯CoffeeComponent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void print(CoffeeComponent coffeeComponent){
out.println(coffeeComponent.getName()+"/"+coffeeComponent.getAdd()+"/"+coffeeComponent.getPrice()+"$");
}

public static void main(String[] args) {
CoffeeComponent coffeeComponent = new BlueCoffee();
print(coffeeComponent);
out.println("======加入牛奶=======");
coffeeComponent = new MilkCoffeeDecorator(coffeeComponent);
print(coffeeComponent);
out.println("======加入薄荷=======");
coffeeComponent = new MintCoffeeDecorator(coffeeComponent);
print(coffeeComponent);
out.println("======一杯薄荷牛奶咖啡=====");
CoffeeComponent mintMilkCoffee = new MintCoffeeDecorator(new MilkCoffeeDecorator(new BlueCoffee()));
print(coffeeComponent);
}
1
2
3
4
5
6
7
蓝山咖啡/ /5.0$
======加入牛奶=======
蓝山咖啡/ 牛奶 /7.0$
======加入薄荷=======
蓝山咖啡/ 牛奶 薄荷 /9.0$
======一杯薄荷牛奶咖啡=====
蓝山咖啡/ 牛奶 薄荷 /9.0$

通过装饰者模式记忆IO类

在理解了装饰者模式后,再来理io的脉络就不是很难了。以下是java中io的类图关系
20190908093820782.jpg
将目光放在字节流上
image.png
用前文提到的模式结构将他们的关系用装饰者模式理一遍

InputStream OutputStream 作为字节流的两个超类(抽象构件 Component),分别是输入与输出
在InputStream这个模式中,拥有 FileInputStream(文件处理),ByteArrayInputStream…. 等具体构件(ConcreteComponent)
同样与具体构件实现了Component的是装饰职责超类 FilterInputStream ,它的继承下定义了三个具体装饰类
BufferedInputStream,DataInputStream,PushbakInputStream;OutStream同样可以这样整理出来

在脑海中整理出模型后,当我们想读取某个文件时终于不用对着文档敲了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
File file = new File("zsh.txt");
try {
//具体构件
FileInputStream inputStream = new FileInputStream(file);
//增加装饰者 缓冲流
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
byte bytes[] = new byte[bufferedInputStream.available()];
while ((bufferedInputStream.read(bytes))!=-1){
out.print(new String(bytes));
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

当使用字符流时也是同样的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.meteor.io;

import java.io.*;

public class Test2 {
public static void main(String[] args) {
File file = new File("zsh.txt");
try {
//具体构件
FileReader fileReader = new FileReader(file);
//装饰组件
BufferedReader bufferedReader = new BufferedReader();
String line = null;
while ((line = bufferedReader.readLine())!=null){
System.out.println(line);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

image.png