装饰者模式:动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方式。
实际上装饰者模式就是将继承和组合结合在了一起。
这里的一个设计原则是:针对扩展开放,对修改关闭
继承:通过继承子类可以拥有自己定义的方法的同时,还能使用父类方法。但继承不便于扩展父类方法的功能。
组合:是将一个对象传入另一个对象中,由另一个对象的方法调用该对象的方法,并扩展响应功能。与继承相比,组合关系的优势就在于不会破坏类的封装性,且具有较好的松耦合性,可以使系统更加容易维护。但是它的缺点就在于要创建比继承更多的类。
代码示例:
1、先建一个Beverage抽象类,作为饮料的基类。
2、然后依次有四种类型的具体饮料实现HouseBlend、DarkRost、Espresso、Decaf,但都继承自Beverage抽象类。
3、然后定义一个装饰者基类CondimentDecorator,代表调味品基类。
4、具体实现的各种调味品的装饰者,Milk、Moch、Soy、Whip
// 饮料的抽象基类
abstract class Beverage{
String description = "Unkonwn Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
// 四种具体实现的饮料(省略另外两种)
class HouseBlend extends Beverage{
public HouseBlend() {
description = "HouseBlend";
}
@Override
public double cost() {
return 1.99;
}
}
class Espresso extends Beverage{
public Espresso() {
description = "Espresso";
}
@Override
public double cost() {
return 2.3;
}
}
装饰者类
// 调料抽象类 装饰者基类
abstract class CondimentDecorator extends Beverage{
public abstract String getDescription();//要求所有具体的调料类必须实现这个方法
}
// 具体调料类
class Mocha extends CondimentDecorator{
Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription()+",Mocha";
}
@Override
public double cost() {
return 0.2+beverage.cost();
}
}
class Milk extends CondimentDecorator{
Beverage beverage;
public Milk(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription()+",Milk";
}
@Override
public double cost() {
return beverage.cost()+0.30;
}
}
调用测试
public static void main(String[] args) {
Beverage b1 = new HouseBlend();
Beverage b2 = new Espresso();
// 针对咖啡添加调料
b1 = new Mocha(b1);// 加摩卡
b1 = new Milk(b1);// 再加牛奶
System.out.println("b1:"+b1.cost()+"$ "+b1.getDescription());
b2 = new Milk(b2);// 只加牛奶
System.out.println("b2:"+b2.cost()+"$ "+b2.getDescription());
}
这就相当与给每种类型的咖啡添加很多调料装饰,这种装饰可以任意添加嵌套。
这里解释一下为什么装饰者基类需要继承自一样的超类Beverage?
因为装饰者需要和被装饰者(也就是包装的组件,初始时即传入的具体咖啡)具有一样的接口,这个可以通过继承实现。继承的目的只是达到类型匹配,使之具有一样的接口,并不是获得父类方法行为,具体的行为来自于每次传入的被包装的组件。
这里又有一个问题:为什么装饰者需要和被装饰者具有一样的接口?
使用装饰者第一次,装饰完后如:b1 = new Mocha(b1);// 加摩卡
,因为装饰者和被装饰者(即组件)继承自同一父类所以具有一样接口,又可以作为一个新的组件传入下一个装饰者进行包装,如:b1 = new Milk(b1);// 再加牛奶
。
运行结果(结果中出现多位小数原因:是因为计算机无法精确的使用二进制描述一些小数,double类型运算时,有时就会遇到这些数,导致使用近似描述造成误差)
b1:2.4899999999999998$ HouseBlend,Mocha,Milk
b2:2.5999999999999996$ Espresso,Milk
实际中java IO就是使用装饰者模式组织的,这里也引出了一个装饰者模式的一个缺点:含有太多的小类,可能会给第一次看到的人困扰,但了解整个装饰者模式的组织架构,在看这些类就很清晰了。