在说装饰者模式之前,先来说一个重要的设计原则:
类应该对扩展开放,对修改关闭。
遵循了这个原则,我们的类将会容易扩展,在不修改现有代码的情况下,我们可以搭配新的行为,这样的设计具有弹性,可以接受新的功能来应对改变的需求。
装饰者模式的定义:动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
接下来,我们以一个卖咖啡的例子来讲解。
在咖啡店,有各种不同的咖啡,都有不同的价格,如黑咖啡(Espresso)售价1.99,客户还可以加不同的调料,不同的调料价格不同。在这个背景下,如果让我们来设计,可能会想到为每一种咖啡配每一种调料设计一个类,然后计算价格,这样的话情况会非常复杂,而且万一我想加两份牛奶呢?这时候,就让我们看一下装饰者模式。
先看一个图:
1.装饰者和被装饰者有相同的超类型,我们利用继承达到“类型匹配”,方便调用,而不是利用继承获得行为。
2.装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。
了解了装饰者模式,接下来要编写代码,也是关于咖啡店的,先看一个简单的类图。
代码:
Coffee.java 抽象的超类,各种类型的咖啡和调料都继承这个超类。
package com.decorator;
//抽象组件类
public abstract class Coffee {
protected String description = "Unknown Coffee";
public String getDescription(){
return this.description;
}
public abstract double cost();
}
Espresso.java 具体的子类,是具体的被装饰者
package com.decorator;
//黑咖啡,是coffee的子类,也就是具体组件类
public class Espresso extends Coffee {
public Espresso(){
this.description = "Espresso";
}
public double cost(){
return 1.99;
}
}
CondimentDecorator.java
package com.decorator;
//调料,继承超类的装饰者抽象类,为了达到“类型匹配”,而不是为了获得行为
public abstract class CondimentDecorator extends Coffee {
//所有的调料装饰者都必须重新实现这个方法,因为装饰者的description是要要修饰被装饰者的description的
public abstract String getDescription();
}
Milk.java
package com.decorator;
//装饰者类
public class Milk extends CondimentDecorator {
private Coffee coffee;
public Milk(Coffee coffee){
this.coffee = coffee;
}
public String getDescription(){//重写该方法,
return coffee.getDescription() + ", Milk";
}
//从装饰者开始调用,一路调用,计算钱
public double cost(){
return 0.2 + coffee.cost();
}
}
测试类:
package com.decorator;
public class StarbuzzCoffee {
public static void main(String[] args) {
Coffee coffee = new Espresso();
System.out.println(coffee.getDescription() + " " + coffee.cost());
Coffee coffee1 = new Espresso();
coffee1 = new Milk(coffee1);//加一份牛奶
System.out.println(coffee1.getDescription() + " " + coffee1.cost());
}
}