《三》装饰者设计模式
在这里直接使用HeadFist设计模式中的星巴兹咖啡店为例(不同品种的咖啡配上各类的调料对应不同的价格),在这里我们首先使用继承的方式来实现:
//抽象父类
public abstract class Beverage {
protected String description;
void getDescription(){
System.out.println(description);
}
//父类中提供计算各类调料的价格
public double cost(){
double sum = 0.0;
if (isMilk()){
sum +=0.5;
}
if (isSoy()){
sum +=0.8;
}
return sum;
}
//用布尔值来表示该调料是否被添加
protected boolean milk;
protected boolean soy;
public boolean isMilk() {
return milk;
}
public void setMilk(boolean milk) {
this.milk = milk;
}
public boolean isSoy() {
return soy;
}
public void setSoy(boolean soy) {
this.soy = soy;
}
}
//焦烤咖啡
public class DarkRoast extends Beverage {
public DarkRoast() {
description="焦烤咖啡";
}
@Override
public double cost() {
return super.cost() + 5.0;
}
}
//测试
public class TestCost {
@Test
public void testCost(){
DarkRoast dr1 = new DarkRoast();
dr1.setMilk(true);
dr1.setSoy(true);
System.out.println("加牛奶和大豆的焦烤咖啡的价格:"+dr1.cost());//加牛奶和大豆的焦烤咖啡的价格:6.3
DarkRoast dr2 = new DarkRoast();
dr2.setMilk(false);
dr2.setSoy(false);
System.out.println("什么都不加的焦烤咖啡的价格:"+dr2.cost());//什么都不加的焦烤咖啡的价格:5.0
}
}
虽然使用继承和实例变量的方法可以实现不同种类咖啡配上不同调料的需求,但是实际上存在以下几个问题:
1.如果咖啡店新加了一种调料,那么就必须对超类进行修改,增加实例变量以及修改cost()
2.不是所有的子类都适合超类中的调料,假如新加了一种冰红茶,那么它明显不适合添加milk这种调料
3.如果顾客需求添加双倍调料的某种咖啡,这种设计就没有办法实现了
装饰者模式:动态的将责任添加到对象上,若要扩展功能,装饰者提供了比继承者更有弹性的替代方案。实际上就是用组合的方式来替代继承。
我们再用装饰者模式把咖啡店的例子实现一次:
//抽象父类(Component)
public abstract class Beverage {
protected String description;
public String getDescription(){
return description;
}
public abstract double cost();
}
//焦烤咖啡(ConcreteComponent)
public class DarkRoast extends Beverage {
public DarkRoast() {
description="焦烤咖啡";
}
@Override
public double cost() {
return 5.0;
}
}
//脱因咖啡(ConcreteComponent)
public class Decaf extends Beverage {
public Decaf() {
description="脱因咖啡";
}
@Override
public double cost() {
return 6.0;
}
}
//调料牛奶(Decorator)
public class Milk extends Beverage {
private Beverage beverage;
public Milk(Beverage beverage) {
this.beverage = beverage;
description = "加了牛奶的"+beverage.description;
}
@Override
public double cost() {
return 1.0+beverage.cost();
}
}
//调料大豆(Decorator)
public class Soy extends Beverage {
private Beverage beverage;
public Soy(Beverage beverage) {
this.beverage = beverage;
description="加了大豆"+beverage.description;
}
@Override
public double cost() {
return 0.5+beverage.cost();
}
}
//测试
public class TestCost {
@Test
public void testCost(){
DarkRoast dr = new DarkRoast();
//什么都不加的焦烤咖啡
System.out.println(dr.getDescription()+"的价格是"+dr.cost());
Milk m = new Milk(dr);
//只加奶的焦烤咖啡
System.out.println(m.getDescription()+"的价格是"+m.cost());
Soy s = new Soy(m);
//加奶加豆的焦烤咖啡
System.out.println(s.getDescription()+"的价格是"+s.cost());
//加了双份牛奶的脱因咖啡
Beverage decaf = new Decaf();
decaf = new Milk(decaf);
decaf = new Milk(decaf);
System.out.println(decaf.getDescription()+"价格是"+decaf.cost());
}
}
装饰者模式的组成:
抽象组件:所有被装饰者和装饰者继承或实现的父接口(父类)
具体被装饰者角色:被动态添加行为的角色
抽象装饰者角色:持有被装饰者角色的引用
具体装饰者角色:具体扩展被装饰者的角色
总结:
我们发现使用装饰者模式也使用到了继承,但是这里的继承并不是
继承行为,而是
继承类型。
优点:
1.各个装饰对象的调用顺序可以随意变化
2.如果需要扩展一个新的功能,只需要增加一个类,添加到指定位置,不必修改原有的代码,很好的体现了开闭原则
3.各个模块可以根据需求随意组合,充分的复用代码。