设计模式-装饰者模式(Decorator Pattern)
本文由@呆代待殆原创,转载请注明出处。
此设计模式遵循的设计原则之一:类应该支持扩展,而拒绝修改(Open-Closed Principle)
装饰者模式简述
装饰者模式通过组合的方式扩展对象的特性,这种方式允许我们在任何时候对对象的功能进行扩展甚至是运行时扩展,而若我们用继承来完成对类的扩展则只能在编译阶段实现,所以在某些时候装饰者模式比继承(inheritance)要更加灵活。
装饰者模式具有的一些特征
1,装饰者(decorator)和被装饰(扩展)的对象有着相同的超类(supertype)。
2,我们可以用多个装饰者去装饰一个对象。
3,我们可以用装饰过的对象替换代码中的原对象,而不会出问题(因为他们有相同的超类)。
4,装饰者可以在委托(delegate,即调用被装饰的类的成员完成一些工作)被装饰者的行为完成之前或之后加上他自己的行为。
5,一个对象能在任何时候被装饰,甚至是运行时。
装饰者模式的基本结构
我们来看一张《Head first 设计模式》里的图
(图中的英文为书中对这个结构的解释与说明,下面的解释并不是对图中英文的直接翻译,而是博主自己的稍稍结合书中其他内容的总结)
Component:一般是一个抽象类(也有可能不是),是一组有着某种用途类的基类,包含着这些类最基本的特性。
ConcreteComponent:继承自Component,一般是一个有实际用途的类,这个类就是我们以后要装饰的对象。
Decorator:继承自Component,装饰者需要共同实现的接口(也可以是抽象类),用来保证装饰者和被装饰者有共同的超类,并保证每一个装饰者都有一些必须具有的性质,如每一个装饰者都有一个实例变量(instance variable)用来保存某个Component类型的类的引用。
ConcreteDecorator:继承自Decorator,用来装饰Component类型的类(不能装饰抽象类),为其添加新的特性,可以在委托被装饰者的行为完成之前或之后的任意时候。
一个简单的实例
我们用《Head First 设计模式》里举的星巴克订单的例子来说明。
星巴克提供不同种类的咖啡和咖啡的调料,星巴克需要一些类来描述他们并且能计算出任意一种咖啡和任意几种调料搭配在一起的价格,如果我们用继承为每一种搭配写一个类的话,就会变成下面这个样子。
要在工作中用这种东西,还不如让我们一起狗带_(:зゝ∠)_。。。。。
当然我们也可把调料都写在作为超类Beverage里,但是这样的话会造成数据的大量冗余,这是一个解决办法,但是还不够好。
如果套用上面所介绍的装饰者模式的结构就是下面这个样子
Beverage(饮料类):相当与Component
HouseBlend、DarkRoast...(混合咖啡类、礁炒咖啡类...):相当于ConcreteComponent
CondimentDecorator(调料装饰者类):相当于Decorator
Milk、Mocha...(牛奶类、摩卡类...):相当于ConcreteDecorator
装饰者模式的特点,一个ConcreteComponent可以被任意个ConcreteDecorator装饰。
结合实例就来解释,一种 咖啡 可以和任意种 调料 搭配。
这样我们来应对各种点单的搭配的时候只需要在一种咖啡(ConcreteComponent)加上各种(ConcreteDecorator)就可以完成了,是不是觉得特别方便。
java实现-装饰者模式-星巴克
1 public abstract class Beverage { 2 protected String description=""; 3 public String getDescription(){ 4 return description; 5 } 6 public abstract double cost(); 7 }
1 public abstract class CondimentDecorator extends Beverage { 2 public abstract String getDescription(); 3 4 }
1 public class HouseBlend extends Beverage { 2 public HouseBlend(){ 3 description="House Blend coffee"; 4 } 5 @Override 6 public double cost() { 7 return 4.9; 8 } 9 }
1 public class Milk extends CondimentDecorator { 2 protected Beverage beverage; 3 public Milk(Beverage beverage){ 4 this.beverage=beverage; 5 } 6 @Override 7 public String getDescription() { 8 return beverage.getDescription()+",with milk"; 9 } 10 @Override 11 public double cost() { 12 return 2.3+beverage.cost(); 13 } 14 }
1 public class Mocha extends CondimentDecorator { 2 protected Beverage beverage; 3 public Mocha(Beverage beverage){ 4 this.beverage=beverage; 5 } 6 @Override 7 public String getDescription() { 8 return beverage.getDescription()+",with Mocha"; 9 } 10 @Override 11 public double cost() { 12 return 1.2+beverage.cost(); 13 } 14 }
1 public class Starbuzz { 2 public static void main(String[] args) { 3 Beverage beverage=new Mocha(new Milk(new Mocha(new HouseBlend()))); 4 //如上就是一杯HouseBlend配上两份Mocha和一份Milk,博主没去星巴克喝过咖啡,这样配可以么= = 5 System.out.println(beverage.getDescription()+":"+beverage.cost()); 6 } 7 8 }
输出如下
House Blend coffee,with Mocha,with milk,with Mocha:9.6
继承与装饰者模式
ps:装饰者模式中用到了继承,但是这里装饰者模式用继承是为了保证装饰者和被装饰者有共同的超类,而不是为了给被装饰的类扩展新的特性,而装饰者模式中新的特性是通过类与类的组合(has-a的关系)而得到的,所以把装饰者模式和继承看做同一类设计思想是不恰当的。
不适合使用装饰者模式的情况
当你的有些设计是针对某些具体的Component类型即ConcreteComponent时,装饰者模式是不适用的,因为被装饰过的类是通过组合来实现特性的扩展,所以装饰过的类并不具有某个特定的ConcreteComponent类型,而只具有其本身的类型和它继承的Decorator以及Component的类型,所以只有当你的操作都是针对Component类型的时候,装饰者模式才是合适的,否则你就要重新考虑你的设计。
装饰者设计模式的缺点
会引入大量的类,常常让人觉得不知所措,特别是对于初学者来说,最好的例子就是java的I/O类,如果你翻一翻API你就会看到多的想让你摔电脑的类名,但是,博主觉得即使存在这个缺点,但装饰者模式仍然是一个值得我们学习和应用的好设计模式。
参考资料:《Head First 设计模式》