装饰者模式(Decorate)
动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
设计原则四:类应该对扩展开放,对修改关闭。
如果使用过 Python,应该听过装饰器,虽然概念有点不同,但都是通过动态添加的方式给对象扩展功能。
栗子
星巴克的订单系统系统中有个饮料抽象类 Beverage,店内的饮料都必须继承该类:
abstract class Beverage {
// 饮料描述,比如咖啡、牛奶
protected String description;
public String getDescription() {
return this.description;
}
// 饮料价钱
public abstract float cost();
}
其中一些饮料:
class Coffee extends Beverage {
public Coffee() {
description = "Coffee";
}
@Override
public float cost() {
return 3.2f;
}
}
class Milk extends Beverage {
public Milk() {
description = "Milk";
}
@Override
public float cost() {
return 4.6f;
}
}
// ...
提需求
现在,客户想要在买的饮料里加点调味料,比如买了杯咖啡,要加点牛奶、豆浆、巧克力等。这就需要订单系统在统计饮料价格时加上调味料的价格。
错误示范1
每种可能的调味料的饮料都新建一个类:
class CoffeeWithMilk extends Beverage {
@Override
public float cost() {
return 7.9f;
}
}
class MilkWithCoffee extends Beverage {
@Override
public float cost() {
return 8.2f;
}
}
// ....
很明显,调味料有很多,饮料也有很多,如果只加一种调味料,那搭配起来也有非常多的可能,如果都新建一个类,那么就会造成“类爆炸”。而且这些调味料的价格如果发生变化,就要将涉及的饮料都修改一遍,严重违反了设计原则中的“将变化与不变化的代码分开”。
错误示范2
既然违反了变化的原则,那么尝试把这些变化的调味料都放在饮料抽象类 Beverage 中?
abstract class Beverage {
protected String description;
// 是否添加了某些调味料
protected boolean milk;
protected boolean coffee;
public String getDescription() {
return this.description;
}
// 计算好添加的调味料价格,让子类直接调用
public float cost() {
float sum = 0;
if (hasCoffee()) {
sum += 3.2f;
}
if (hasMilk()) {
sum += 4.6f;
}
return sum;
}
protected boolean hasCoffee() {
return this.coffee;
}
protected void setCoffee(boolean coffee) {
this.coffee = coffee;
}
protected boolean hasMilk() {
return this.milk;
}
protected void setMilk(boolean milk) {
this.milk = milk;
}
}
饮料只要设置好添加的调味料,最后计算下自身花费就好了:
class Coffee extends Beverage {
@Override
public float cost() {
return super.cost() + 3.2f;
}
}
class Milk extends Beverage {
@Override
public float cost() {
return super.cost() + 4.6f;
}
}
调用:
public class Main {
public static void main(String[] args) {
Coffee coffee = new Coffee();
// 加点牛奶
coffee.setMilk(true);
System.out.println(coffee.cost());
}
}
现在没有了“类爆炸”,就算调料价格发生变化,只要修改下抽象类就好了,但是真的没问题了吗?
有问题,而且挺多的:
1. 如果出现新的调味料,就需要在抽象类添加新的布尔值成员,cost 方法也需要添加新的判断,还要分别新增一个 setter 和一个 getter,如果是删除一个调味料呢?
2. 对于有些饮料来说,某些调味料并不能加(会拉肚子的!),比如冰红茶 + 牛奶?
3. 如果客户要一杯咖啡,加两份牛奶呢?
满足需求
不改变原有的饮料抽象和具体的饮料:
abstract class Beverage {
protected String description;
public String getDescription() {
return this.description;
}
public abstract float cost();
}
class Coffee extends Beverage {
public Coffee() {
description = "Coffee";
}
@Override
public float cost() {
return 3.2f;
}
}
添加调味料(condiment)装饰者:
abstract class CondimentDecorator extends Beverage {
@Override
public abstract String getDescription();
}
调味料继承调味料装饰者:
class Milk extends CondimentDecorator {
Beverage beverage;
public Milk(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + " + milk";
}
@Override
public float cost() {
return .50f + beverage.cost();
}
}
class Mocha extends CondimentDecorator {
Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + " + mocha";
}
@Override
public float cost() {
return .20f + beverage.cost();
}
}
测试
public class Main {
public static void main(String[] args) {
Beverage coffee1 = new Coffee();
// 不需要调味料
System.out.println(coffee1.getDescription() + " $" + coffee1.cost());
Beverage coffee2 = new Coffee();
// 加牛奶和摩卡
coffee2 = new Milk(coffee2);
coffee2 = new Mocha(coffee2);
System.out.println(coffee2.getDescription() + " $" + coffee2.cost());
Beverage coffee3 = new Coffee();
// 加3份牛奶和2份摩卡
coffee3 = new Milk(coffee3);
coffee3 = new Milk(coffee3);
coffee3 = new Milk(coffee3);
coffee3 = new Mocha(coffee3);
coffee3 = new Mocha(coffee3);
System.out.println(coffee3.getDescription() + " $" + coffee3.cost());
}
}
输出:
Coffee
3.2Coffee+milk+mocha
3.2
C
o
f
f
e
e
+
m
i
l
k
+
m
o
c
h
a
3.9
Coffee + milk + milk + milk + mocha + mocha $5.0999994