设计模式(六)之结构型模式--装饰者模式

一、装饰者模式

1.1 案例分析

描述:

​ 星巴克咖啡订单项目(咖啡馆):

  1. 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无音咖啡)
  2. 调料:Milk、Soy(豆浆)、Chocolate

要求:

​ 在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便

费用:

​ 在计算不同种类咖啡的费用:客户可以点单品咖啡,也可以单品咖啡+调料组合

1.1.1 解析—方案1

方案1如下图所示:

(1)将咖啡抽象为一个类:Drink

(2)使用单品咖啡继承该抽象类

(3)使用单品咖啡+调料来组合继承该抽象类

image-20210706223036942

1.1.1.1 方案1问题分析

  1. Drink是一个抽象类,表示饮料
  2. description就是对咖啡的描述,比如咖啡的名字
  3. cost()方法就是计算费用,Drink类中做成一个抽象方法
  4. Decaf就是单品咖啡,继承Drink,并实现cost
  5. Espress&&Milk就是单品咖啡+调料,这个组合很多

问题:

  • 这样设计,会有很多类,当我们增加一个单品咖啡,或者一个新的调料,类的数量就会暴增,就会出现类爆炸

1.1.2 解析—方案2

方案1因为单品咖啡+调料组合会造成类的倍增。因此可以做改进,将调料内置到Drink类,这样就不会造成类数量过多

方案2如下图所示:

(1)将调料写在抽象类中,避免类过多

(2)实现has方法,来确定是否存在某些调料,set方法时,可用Boolean型,表示是否要添加相应的调料

(3)当某些调料有多份时,可以用int型

image-20210706225407778

1.1.2 方案2问题分析

  1. 方案2可以控制类的数量,不至于造成很多的类
  2. 在增加或者删除调料种类时,代码的维护量很大
  3. 考虑到用户可以添加多份调料时,可以将hasMilk返回一个对应int
  4. 考虑使用装饰者模式

1.2 装饰者模式定义

装饰者模式:动态的将新功能附加到对象上。

在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则

1.3 装饰者模式(Decorator)原理

image-20210706231849737

  • Component:主体,类似于前面的Drink
  • ConcreteComponent:具体的主体,比如前面的各个单品咖啡
  • Decorator:装饰者,比如各调料

1.4 装饰者模式解决咖啡订单问题

  • Drink类就是前面说的抽象类,也就是Component
  • ShortBlack就是单品咖啡
  • Decorator是一个装饰类,含有一个被装饰的对象(Drink obj)
  • Decorator的cost方法 进行一个费用的叠加计算,递归的计算价格

1.5 装饰者模式图解

img

  • LongBlack是一个被装饰对象
  • Milk包含了LongBlack,用来装饰LongBlack
  • 将Mink & LongBlack看着一个被装饰对象
  • Chocolate包含了Mink & LongBlack,用来装饰Mink & LongBlack

1.6 代码实例

1.6.1 Drink抽象类

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
public abstract class Drink {

public String des;//描述
private float price = 0.0f;

public String getDes() {
return des;
}

public void setDes(String des) {
this.des = des;
}

public float getPrice() {
return price;
}

public void setPrice(float price) {
this.price = price;
}

//计算费用的抽象方法
//子类来实现
public abstract float cost();
}

1.6.2 Coffee类:继承Drink

1
2
3
4
5
6
public class Coffee extends Drink {
@Override
public float cost() {
return super.getPrice();
}
}

1.6.3 Espresso类:继承Coffee

1
2
3
4
5
6
public class Espresso extends Coffee {
public Espresso() {
setDes("意大利咖啡");
setPrice(2.0f);
}
}

1.6.4 LongBlack类:继承Coffee

1
2
3
4
5
6
public class LongBlack extends Coffee {
public LongBlack() {
setDes("LongBlack");
setPrice(6.0f);
}
}

1.6.5 ShortBlack类:继承Coffee

1
2
3
4
5
6
public class ShortBlack extends Coffee {
public ShortBlack() {
setDes("ShortBlack");
setPrice(4.0f);
}
}

1.6.6 Deorator类:装饰类:继承Drink,并且组合Drink

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Decorator extends Drink {

private Drink obj;

public Decorator(Drink obj) {//组合
this.obj = obj;
}

@Override
public float cost() {
//getPrice:自己价格
return super.getPrice() + obj.cost();
}

@Override
public String getDes() {
//obj.getDes() 被装饰者的信息
return super.des + " " + super.getPrice() + "&&" + obj.getDes();
}
}

1.6.7 Chocolate类:继承Decorator

1
2
3
4
5
6
7
8
9
//具体的Decorator,这里就是调味品
public class Chocolate extends Decorator {

public Chocolate(Drink obj) {
super(obj);
setDes(" 巧克力 ");
setPrice(3.0f);//调味品的价格
}
}

1.6.8 Milk类:继承Decorator

1
2
3
4
5
6
7
public class Milk extends Decorator {
public Milk(Drink obj) {
super(obj);
setDes("牛奶");
setPrice(2.0f);
}
}

1.6.9 Soy类:继承Decorator

1
2
3
4
5
6
7
public class Soy extends Decorator {
public Soy(Drink obj) {
super(obj);
setDes(" 豆浆 ");
setPrice(1.5f);
}
}

1.6.10 CoffeeBar类:客户端类

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 CoffeeBar {
public static void main(String[] args) {
//装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack

//1.点一份LongBlack
Drink order = new LongBlack();
System.out.println("费用1="+order.cost());
System.out.println("描述="+order.getDes());

//2.order 加入一份牛奶
order = new Milk(order);
System.out.println("order 加入一份牛奶 费用= "+order.cost());
System.out.println("order 加入一份牛奶 描述="+order.getDes());

//3.order 加入一份巧克力
order = new Chocolate(order);
System.out.println("order 加入一份牛奶 加入一份巧克力 费用= "+order.cost());
System.out.println("order 加入一份牛奶 加入一份巧克力 描述="+order.getDes());

//4.order 加入一份巧克力
order = new Chocolate(order);
System.out.println("order 加入一份牛奶 加入2份巧克力 费用= "+order.cost());
System.out.println("order 加入一份牛奶 加入2份巧克力 描述="+order.getDes());

}
}

运行结果:

1
2
3
4
5
6
7
8
费用1=6.0
描述=LongBlack
order 加入一份牛奶 费用= 8.0
order 加入一份牛奶 描述=牛奶 2.0&&LongBlack
order 加入一份牛奶 加入一份巧克力 费用= 11.0
order 加入一份牛奶 加入一份巧克力 描述= 巧克力 3.0&&牛奶 2.0&&LongBlack
order 加入一份牛奶 加入2份巧克力 费用= 14.0
order 加入一份牛奶 加入2份巧克力 描述= 巧克力 3.0&& 巧克力 3.0&&牛奶 2.0&&LongBlack

分析:使用装饰者模式的好处?

  • 比如想加入一个Decaf

1.6.11 Decaf类:继承Coffee

1
2
3
4
5
6
public class Decaf extends Coffee {
public Decaf() {
setDes("Decaf");
setPrice(1.0f);
}
}

1.6.12 CoffeeBar类:客户端类

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
public class CoffeeBar {
public static void main(String[] args) {
//装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack

//1.点一份LongBlack
Drink order = new LongBlack();
System.out.println("费用1="+order.cost());
System.out.println("描述="+order.getDes());

//2.order 加入一份牛奶
order = new Milk(order);
System.out.println("order 加入一份牛奶 费用= "+order.cost());
System.out.println("order 加入一份牛奶 描述="+order.getDes());

//3.order 加入一份巧克力
order = new Chocolate(order);
System.out.println("order 加入一份牛奶 加入一份巧克力 费用= "+order.cost());
System.out.println("order 加入一份牛奶 加入一份巧克力 描述="+order.getDes());

//4.order 加入一份巧克力
order = new Chocolate(order);
System.out.println("order 加入一份牛奶 加入2份巧克力 费用= "+order.cost());
System.out.println("order 加入一份牛奶 加入2份巧克力 描述="+order.getDes());

System.out.println("===================================");
Drink order2 = new Decaf();
System.out.println("费用1="+order2.cost());
System.out.println("描述="+order2.getDes());

order = new Chocolate(order2);
System.out.println("order 加入一份牛奶 费用= "+order.cost());
System.out.println("order 加入一份牛奶 描述="+order.getDes());

}
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
费用1=6.0
描述=LongBlack
order 加入一份牛奶 费用= 8.0
order 加入一份牛奶 描述=牛奶 2.0&&LongBlack
order 加入一份牛奶 加入一份巧克力 费用= 11.0
order 加入一份牛奶 加入一份巧克力 描述= 巧克力 3.0&&牛奶 2.0&&LongBlack
order 加入一份牛奶 加入2份巧克力 费用= 14.0
order 加入一份牛奶 加入2份巧克力 描述= 巧克力 3.0&& 巧克力 3.0&&牛奶 2.0&&LongBlack
===================================
费用1=1.0
描述=Decaf
order 加入一份牛奶 费用= 4.0
order 加入一份牛奶 描述= 巧克力 3.0&&Decaf

1.7 装饰者模式在JDK应用的源码分析

以java.io流为例进行分析,在看InputStream的实现类时,主要看其中的IO包下的类

image-20210712225543454

1.7.1 源码客户端

1
2
3
4
5
6
7
8
9
10
import java.io.DataInputStream;
import java.io.FileInputStream;

public class yuanma {
public static void main(String[] args) throws Exception{
DataInputStream dis = new DataInputStream(new FileInputStream("D:\\abc.txt"));
System.out.println(dis.read());
dis.close();
}
}

1.7.2 InputStream类:抽象类

1
2
3
4
5
6
public abstract class InputStream implements Closeable {

// MAX_SKIP_BUFFER_SIZE is used to determine the maximum buffer size to
// use when skipping.
private static final int MAX_SKIP_BUFFER_SIZE = 2048;
}

1.7.3 FileInputStream源码:继承InputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public
class FileInputStream extends InputStream
{
/* File Descriptor - handle to the open file */
private final FileDescriptor fd;

/**
* The path of the referenced file
* (null if the stream is created with a file descriptor)
*/
private final String path;

private FileChannel channel = null;

private final Object closeLock = new Object();
private volatile boolean closed = false;

FileNotFoundException {
this(name != null ? new File(name) : null);
}

1.7.4 FilterInputStream类:继承InputStream,并且组合InputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public
class FilterInputStream extends InputStream {
/**
* The input stream to be filtered.
*/
protected volatile InputStream in;

/**
* 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 FilterInputStream(InputStream in) {
this.in = in;
}
}

1.7.5 DataInputStream源码:继承FilterInputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DataInputStream extends FilterInputStream implements DataInput {

/**
* Creates a DataInputStream that uses the specified
* underlying InputStream.
*
* @param in the specified input stream
*/
public DataInputStream(InputStream in) {
super(in);
}

/**
* working arrays initialized on demand by readUTF
*/
private byte bytearr[] = new byte[80];
private char chararr[] = new char[80];
}

结合源码分析:

  1. InputStream是抽象类,类似于我们前面的Drink
  2. FileInputStream是InputStream子类,类似于我们前面的Decaf、LongBlack
  3. FilterInputStream是InputStream子类,类似于我们前面的Decorator修饰者,含有 protected volatile InputStream in;
  4. DataInputStrea是FilterInputStream子类,具体的修饰者,类似前面的Milk,Soy等
  5. 分析得出,在jdk的io体系中,就是使用的装饰者模式
0%