装饰者模式

需求介绍

星巴克咖啡订单项目。

  • 咖啡种类:Espresso(意大利咖啡)、ShortBlack(浓缩咖啡)、LongBlank(美式咖啡)、Decaf(无因咖啡)
  • 调料:Milk、Soy(豆浆)、Chocolate
  • 要求在扩展新的咖啡种类时候,具有良好的扩展性、改动方便、维护方便
  • 使用OO的来计算不同种类咖啡的费用:客户可以点单品咖啡,也可以点咖啡+调料组合。

传统方式

思路1

  • Drink 是一个抽象类,标识饮料
  • des就是对咖啡的描述,比如咖啡的名称
  • cost()方法是计算费用,Drink类中做成一个抽象方法
  • Decaf就是单品咖啡,集成Drink,并实现cost
  • Espress && Mikl 就是单品咖啡+调料,这个组合很多

image-20190901110606741

优缺点

  • 思路简单。
  • 这样设计,会有很多类,当我们新家一个单品咖啡,或者一个新的调料,类的数据就会倍增(单品+调料组合),导致类爆炸。

改进思路

咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内置到Drink类,这样就不会造成类数量过多,从而提高项目的维护成本,具体看思路2。

思路2

  • milk、soy、chocolate 可以设计为Boolan,表示是否要添加响应的调料

image-20190901111323502

优缺点

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

基本介绍

  • 装饰者模式(Decorator Pattern):允许向一个现有的对象添加新的功能,同时又不改变其结构,动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式体现了开闭原则。
  • 装饰者设计模式属于结构型模式,它是作为现有的类的一个包装。
  • 装饰者模式,又称为:装饰器模式、Wrapper、Decorator
  • 原型模式中包含如下要素:
    • Component:(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。
    • ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。
    • Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。。
    • ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。
    • image-20190901115310876

使用装饰者模式

思路

img

代码

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
26
27
28
public abstract class Drink {
/**
* 描述
*/
private String des;
/**
* 金额
*/
private float price = 0.0f;

public abstract float cost();

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;
}
}

Coffee

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

Espresso、LongBlack、ShortBlack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @author kongliufeng
*/
public class Espresso extends Coffee {
public Espresso() {
setDes("意大利咖啡");
setPrice(6);
}
}

public class LongBlack extends Coffee{
public LongBlack() {
setDes("美式咖啡");
setPrice(5);
}
}

public class ShortBlack extends Coffee{
public ShortBlack() {
setDes("浓缩咖啡");
setPrice(4);
}
}

Decorator

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

//聚合
public Decorator(Drink drink) {
this.drink = drink;
}

@Override
public float cost() {
//调料的价格+咖啡的价格
return super.getPrice() + drink.cost();
}

@Override
public String getDes() {
return super.getDes() + " && " + drink.getDes();
}
}

Chocolate、Milk、Soy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* @author kongliufeng
*/
public class Chocolate extends Decorator{
public Chocolate(Drink drink) {
super(drink);
setDes("巧克力");
setPrice(1);
}
}
public class Milk extends Decorator{
public Milk(Drink drink) {
super(drink);
setDes("牛奶");
setPrice(1);
}
}
public class Soy extends Decorator{
public Soy(Drink drink) {
super(drink);
setDes("豆浆");
setPrice(2);
}
}

FoldedPhone

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
/**
* @author kongliufeng
*/
public class FoldedPhone extends Phone{

public FoldedPhone() {
}

public FoldedPhone(Brand brand) {
super(brand);
}

public void open(){
super.open();
System.out.println("折叠样式手机");
}

public void close(){
super.close();
System.out.println("折叠样式手机");
}

public void call(){
super.call();
System.out.println("折叠样式手机");
}
}

Client

image-20190901114834613

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @author kongliufeng
*/
public class Client {
public static void main(String[] args) {
// 点一份咖啡
Drink order = new LongBlack();
System.out.println(order.cost() + "$\t" + order.getDes());

// 再加一份牛奶
order = new Milk(order);
System.out.println(order.cost() + "$\t" + order.getDes());

// 再加两份巧克力
order = new Chocolate(order);
System.out.println(order.cost() + "$\t" + order.getDes());
order = new Chocolate(order);
System.out.println(order.cost() + "$\t" + order.getDes());
}
}

优缺点

  • 如果你希望在无需修改代码的情况下即可使用对象,且希望在运行时为对象新增额外的行为,可以使用装饰模式。
  • Decorator模式与继承关系的目的都是要扩展对象的功能,但是Decorator可以提供比继承更多的灵活性。
  • 这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。
  • 装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,以及装饰者是否合适。当然也可以改变Component接口,增加新的公开的行为,实现“半透明”的装饰者模式。在实际项目中要做出最佳选择。

应用场景

  • Java JDK 源码 java.io.BufferedInputStream(InputStream)
    • InputStream 是抽象类,类似于Drink
    • FileInputStream 是 InputStrem子类, 类似于Espresso、LongBlack、ShortBlack
    • FilterInputStream 是 InputStrem子类,类似于Decorator修饰类
    • DataInputStream 是 FilterInputStream 子类,具体的修饰者,类似Milk、Soy
    • FilterInputStream 类中有 protected volatile InputStream in ;即含被修饰者
    • 如:DataInputStream dis = new DataInputStream(new FileInputStream(“D:/txt.txt”));

image-20190901120832763