装饰者模式(Decorate)
动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
设计原则四:类应该对扩展开放,对修改关闭。
如果使用过 Python,应该听过装饰器,虽然概念有点不同,但都是通过动态添加的方式给对象扩展功能。
栗子
星巴克的订单系统系统中有个饮料抽象类 Beverage,店内的饮料都必须继承该类:
1 2 3 4 5 6 7 8 9 10 11
| abstract class Beverage { protected String description;
public String getDescription() { return this.description; }
public abstract float cost(); }
|
其中一些饮料:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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
每种可能的调味料的饮料都新建一个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class CoffeeWithMilk extends Beverage { @Override public float cost() { return 7.9f; } }
class MilkWithCoffee extends Beverage { @Override public float cost() { return 8.2f; } }
|
很明显,调味料有很多,饮料也有很多,如果只加一种调味料,那搭配起来也有非常多的可能,如果都新建一个类,那么就会造成“类爆炸”。而且这些调味料的价格如果发生变化,就要将涉及的饮料都修改一遍,严重违反了设计原则中的“将变化与不变化的代码分开”。
错误示范2
既然违反了变化的原则,那么尝试把这些变化的调味料都放在饮料抽象类 Beverage 中?
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 36 37 38
| 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; } }
|
饮料只要设置好添加的调味料,最后计算下自身花费就好了:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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; } }
|
调用:
1 2 3 4 5 6 7 8
| public class Main { public static void main(String[] args) { Coffee coffee = new Coffee(); coffee.setMilk(true); System.out.println(coffee.cost()); } }
|
现在没有了“类爆炸”,就算调料价格发生变化,只要修改下抽象类就好了,但是真的没问题了吗?
有问题,而且挺多的:
- 如果出现新的调味料,就需要在抽象类添加新的布尔值成员,cost 方法也需要添加新的判断,还要分别新增一个 setter 和一个 getter,如果是删除一个调味料呢?
- 对于有些饮料来说,某些调味料并不能加(会拉肚子的!),比如冰红茶 + 牛奶?
- 如果客户要一杯咖啡,加两份牛奶呢?
满足需求
不改变原有的饮料抽象和具体的饮料:
1 2 3 4 5 6 7 8 9
| abstract class Beverage { protected String description;
public String getDescription() { return this.description; }
public abstract float cost(); }
|
1 2 3 4 5 6 7 8 9 10
| class Coffee extends Beverage { public Coffee() { description = "Coffee"; }
@Override public float cost() { return 3.2f; } }
|
添加调味料(condiment)装饰者:
1 2 3 4
| abstract class CondimentDecorator extends Beverage { @Override public abstract String getDescription(); }
|
调味料继承调味料装饰者:
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
| 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(); } }
|
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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(); 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.2
Coffee + milk + mocha $3.9
Coffee + milk + milk + milk + mocha + mocha $5.0999994