装饰者模式:
动态的将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
新的包装类肯定要持有原有基类的句柄,然后覆盖超类中的方法。我们把被包装的类传入包装类,新的包装类就可以调用被包装类的方法并在此基础上做出改动。因为面向对象语言的上溯造型,在需要被包装类的地方完全可以提供包装类。
Java语言的I/O流系统就是装饰者模式的非常典型的代表。
示例:星巴兹咖啡订单系统。星巴兹提供多种口味咖啡并且提供向咖啡中添加各种调料的服务。
错误实现1:
定义一个Beverage抽象超类,店内所有饮料必须继承此类。其含有一个cost()抽象方法,子类必须定义自己的实现。
但是这样一来,添加不同的调料需要写不同的cost(),更进一步,如果调料可以添加多种或者指定双份呢?这种方法不禁实现困难,而且维护困难。如果牛奶价格上涨,每一个包含牛奶的方法都要改动。
错误实现2:
将各种调料以实例变量的形式加入超类中,利用实例变量和继承,就可以追踪这些调料。
这样设计,调料的价格改变会使我们更改现有代码;一旦出现新的调料,就要添加新的方法;以后可能会添加新的饮料,对这些新饮料来说一些调料可能并不适合添加。
设计原则--类应该对扩展开放,对修改关闭。
正确实现:
例如需要一份摩卡奶泡深焙咖啡:1、拿出一个深焙咖啡对象;2、用摩卡对象装饰它;3、用奶泡对象装饰它;4、调用cost()方法,并依赖委托将调料价格加上去。
- 装饰者和被装饰者有相同的超类型
- 既然有相同的超类型,你可以在任何需要原始对象(被包装的)的场合,用装饰过的对象代替它。
- 装饰者可以在所委托被装饰者的行为之前/之后,加上自己的行为,以打到特定的目的。
- 对象可以在任何时候被装饰。
具体Java实现中需要注意:
- 抽象饮料超类是所有具体饮料类的父类,配料抽象类也要继承饮料超类,这样各种具体的包装类也都可以上溯造型为饮料类。
- 包装类需要持有一个饮料超类的句柄。我们把需要被包装的类传给这个句柄,由包装类调用被包装类的方法,然后在此基础上添加自己的包装行为。
代码实现:
//饮料超类
public abstract class Beverage{String description = "null"; //饮料的名称public String getDescription(){//返回饮料的名称return description;}//计算饮料价格的抽象方法public abstract double cost();
}//调料抽象类,也就是装饰者类,装饰者类是饮料超类的一个子类,所以以后的各种包装者类都是饮料超类的子类。
public abstract class CondimentDecorator extends Beverage{public abstract String getDescription();//所有调料装饰者都必须重新弄实现该方法
}//实现Espresso饮料
public class Espresso extends Beverage{public Espresso(){ description = "Espresso"; }//重写价格方法public double cost(){ return 1.99; }
}//实现添加摩卡的Espresso饮料
public class Mocha extends CondimentDecorator{Beverage beverage; //持有饮料超类类的句柄public Mocha(Beverage beverage){this.beverage = beverage;}//重写饮料名称方法public String getDescription(){return beverage.getDescription() + ",Mocha";}//重写计算价格方法public double cost(){ return .20 + beverage.cost(); }
}
要点:
- 继承属于扩展形式之一,但不见得是达到弹性设计的最佳方案。
- 装饰者模式也可以让我们扩展行为。
- 装饰者模式意味着一群装饰者类,这些类用来包裹具体组件。
- 装饰者类反映出被装饰的组件类型(其实他们具有相同的类型,都经过接口或继承实现)。
- 装饰者可在被装饰者的行为前面/后面加上自己的行为甚至整个取代掉,而达到特定的目的。