设计模式(三):装饰器模式
一、概述
装饰器模式动态地将责任附加到对象上。想要扩展功能,装饰者提供了有别于继承的另一种选择。简单描述就是包装对象,让对象提供新的行为。
二、解决问题
当一个类想要获得一个行为,我们会想到面向对象四大特性之一的继承,继承能够让子类从父类中获得行为,实现很好的代码复用。但这种继承而来的行为是在编译时静态决定的,而且所有的子类都会继承相同的行为。如果我们想要扩展对象的行为,就要创建一个子类来修改父类的方法(也就是覆盖父类行为),每扩展一个行为就要创建一个子类,这样会带来很多问题。第一,如果需要扩展的行为有很多,则子类就要创建无数多个(导致类爆炸)。第二、如果子类中的行为需要依赖某个成员变量,当这个成员变量发生改变,子类的代码就要修改(代码维护噩梦)。
如上图,getCost()方法获取茶的价钱,我们每新上市一种茶饮料就要创建一个子类;当茶的某些配料价格发生变化时,我们要修改子类代码。
装饰者模式就是解决以上的问题的,它利用组合的做法扩展对象行为,可以在运行时动态地进行扩展,写新的代码添加新功能,而无须修改现有代码。
三、结构类图
四、成员角色
1.抽象组件(Component)角色:定义一个将要接收附加责任的类,即继承该抽象类的类都有了装饰和被装饰的能力。
2.具体组件(ConcreteComponent)角色:可以被动态加上新行为,被装饰者修饰的类。
3.装饰者(Decorator)角色:装饰者抽象类,继承该类都具有装饰者的能力。
4.具体装饰者(ConcreteDecorator)角色:为具体组件添加新行为。
五、应用实例
下面我们用奶茶饮料价格的例子来解析装饰者的用法,买奶茶的时候我们可以可以买纯奶茶,也可以添加珍珠,咖啡等配料,但价格肯定不一样
//首次我们创建抽象组件,也就是茶饮料
public abstract class Tea { String description = "Unknown Tea"; //茶的描述 public String getDescription(){ return description; } //返回茶的价钱 public abstract double getCost(); }
//接着创建抽象配料,就是装饰者
public abstract class CondimentDecorator extends Tea{ //所有调料必须重新实现描述方法 public abstract String getDescription(); }
//创建奶茶,具体组件,继承抽象组件,被装饰的对象
public class MilkTea extends Tea{ public MilkTea(){ description = "奶茶"; } @Override public double getCost() { //返回奶茶价格 return 3.0; } }
//创建珍珠配料,就是具体的装饰者,继承抽象配料,实现对奶茶的装饰
public class Pearl extends CondimentDecorator{ //持有对所装饰对象的引用 private Tea tea; public Pearl(Tea tea){ this.tea = tea; } @Override public String getDescription() { return "珍珠," + tea.getDescription() ; } @Override public double getCost() { //把茶的价格加上珍珠的价格,得到最后结果 return 1.0 + tea.getCost(); } }
//创建咖啡配料,就是具体的装饰者,继承抽象配料,实现对奶茶的装饰
public class Coffee extends CondimentDecorator{ //持有对所装饰对象的引用 private Tea tea; public Coffee(Tea tea){ this.tea = tea; } @Override public String getDescription() { //加上咖啡后的描述 return "咖啡," + tea.getDescription(); } @Override public double getCost() { //茶的价格加上咖啡的价格,算出结果 return 2.0 + tea.getCost(); } }
//测试装饰者
public class TestDecorator { public static void main(String[] args){ //创建一杯纯奶茶,不需要加调料,打印出描述和价格 Tea tea = new MilkTea(); System.out.println(tea.getDescription() + " 价格为:¥" + tea.getCost()); //创建加调料的奶茶 Tea tea2 = new MilkTea(); //加上一份珍珠 tea2 = new Pearl(tea2); //再加一份珍珠 tea2 = new Pearl(tea2); //加上一份咖啡调料 tea2 = new Coffee(tea2); //打印加了调料的奶茶 System.out.println(tea2.getDescription() + " 价格为:¥" + tea2.getCost()); } }
运行结果:
装饰者在Java I/O中的使用
Java I/O 是使用装饰者模式最典型的例子,看下面InputStream类图可知
六、优点和缺点
1.优点
(1)、利用组合和委托动态地扩展行为,而无须修改现有代码。
(2)、可以用多个装饰者包装一个对象,得到不同的对象组合,从而使被包装的类的功能更加强大。
(3)、装饰者和被装饰者都具有相同的超类,在需要被装饰者的场合,可以用装饰过的对象替代原被装饰者。
(4)、装饰者和被装饰者是相互独立的,我们可以根据需要增加和改变具体组件和具体装饰者,最后通过组合方式使用它们,符合“开闭原则”。
(5)、避免了通过继承方式扩展对象功能所带来的灵活性差,子类无限扩张的问题(类爆炸)。
2.缺点
(1)、导致设计中出现很多小对象,如果过度使用,会让程序变得复杂。
(2)、装饰链过多会使程序效率低,也增加了使用和排错的难度。
七、使用场景
1、当需要动态得扩展对象行为,或者说是动态地增加功能时使用。
2、当使用继承方式对系统功能扩展受到限制时,或者继承不利于系统扩展和维护时。
八、总结
1、装饰者和被装饰对象有相同的超类型。
2、可以用一个或者多个装饰者包装一个对象。
3、可以在任何时候,使用不同的装饰者组合来装饰对象。
4、装饰者可以在被装饰者的行为前面、后面加上自己的行为,甚至取代被装饰者的行为,达到特定的目的。