设计原则:
类应该对扩展开放,对修改关闭
装饰者设计模式
动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
主要思想
为已有功能动态地添加更多功能的一种方式。
从代码实现的角度来说,就是希望在不修改任何底层代码的情况下为对象赋予新的功能。当系统需要添加新功能的时候,通过新的代码来装饰原有类的核心职责或主要行为。这些新加入的代码相当于对原有核心代码的修饰,只在特定的应用场景下出现。
装饰者模式通过组合的方式来扩展对象的行为,而不依赖于继承,也就是说虽然类的框架中包含继承,但只是为了获取正确的类型,而不是继承一种行为。行为来自于装饰者和基础组件,或者与其他装饰者之间的组合关系。
- 装饰者和被装饰对象有相同的超类型。
- 你可以用一个或多个装饰者包装一个对象。
- 既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。
- 装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。
- 对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象
类图如下:
Component类是基本的被装饰对象抽象类,定义了一些功能接口,ConcreteComponent是一个实际的功能类实现。Decorator则是需要为原始核心功能类动态添加功能时的装饰类。ConcreteDecorator是装饰类的实现,包含了一个封装的Component对象(实例变量),每个装饰者功能独立,只负责对自己包含的对象进行自身功能添加,不关注其他功能。装饰的时候考虑先后顺序(如果需要的话)。装饰者还可以添加新的行为,定义新的状态,新的行为是在旧的行为前面或者后面添加一个计算来实现,新结果的获取将依赖或者委托给旧行为的执行结果。
一个装饰者模式的实际应用是: java io里面的InputStream提供基本的按照流读入一个流,而FileInputStream,StringBufferInputStream,ByteArrayInputStream分别作为几种不同类型的流读取方式。而针对FileInputStream又包含诸如BufferedInputStream,PushBackInputStream这些装饰者为文件输入流提供新的功能,处理不同的应用需求。
缺陷
装饰者模式的劣势在于如果客户程序依赖于组件的某种特殊类型,由于装饰是透明的,将出现问题。装饰者模式会导致设计过程中出现许多小对象(每次装饰都需要对象作为新的组件),过渡使用程序会变得复杂。比如在实例化组件的过程中,不仅需要实例化组件,还需要把组件包装进装饰者中,这个问题可以结合工厂模式和生成器模式解决。
代码
相当于抽象的Compoent类
java
public abstract class Beverage { protected String description = "Unknown Beverage"; public String getDescription(){ return description; } public abstract float cost(); }
四个具体组件,每个代表一种咖啡类型
java
public class DarkRoast extends Beverage { public DarkRoast(){ description = "Most Excellent Dark Roast"; } public float cost(){ return 1.05f; } }
抽象的Decorator类
java
public abstract class CondimentDecorator extends Beverage { public abstract String getDescription(); }
具体的装饰者类,每个代表一种扩展功能:调料装饰者
java
// specific decorator public class Mocha extends CondimentDecorator { Beverage beverage; public Mocha(Beverage beverage){ this.beverage = beverage; } @Override public String getDescription() { // TODO Auto-generated method stub return beverage.getDescription() + ", Mocha"; } @Override public float cost() { // TODO Auto-generated method stub return .20f+beverage.cost(); } }
测试类
java
public class StarbuzzCoffee { public static void main(String[] args) { Beverage beverage = new Espresso(); // A cup of espresso without any condiment System.out.println(beverage.getDescription()+"$"+beverage.cost()); // A cup of darkroast with condiment Beverage beverage2 = new DarkRoast(); beverage2 = new Mocha(beverage2); // decorate with mocha beverage2 = new Mocha(beverage2);// decorate with mocha again beverage2 = new Whip(beverage2); // decorate with whip System.out.println(beverage2.getDescription()+"$"+beverage2.cost()); // A cup of houseblend with condiment Beverage beverage3 = new HouseBlend(); beverage3 = new Soy(beverage3); // decorate with mocha beverage3 = new Mocha(beverage3);// decorate with mocha again beverage3 = new Whip(beverage3); // decorate with whip System.out.println(beverage3.getDescription()+"$"+beverage3.cost()); } }