前言
在食品工业中的装饰图案具有比较广泛的应用,大多数的两个图案和在网上的例子饮食相关的,一旦被称为电影的手表,点咖啡要加糖要加奶昔要加这加那的时候。感觉好有派~好高大上啊~。为啥我在小卖部都是“来瓶汽水”就没话说了呢~,难道是我不会“装”?
官方定义
动态的给一个对象加入一些职责,就添加功能来说。该模式比生成子类更为灵活——GOF
Decorator模式是一种相对简单的对象结构性模式,动态和对象是个相应的关系。正如静态和类这种相应关系,编译时可以决定的特质是静态特质,动态则表示在执行时进行操作,传统情况下使用的继承是静态的给类加入职责。动态的给对象加入职责。则是装饰者模式所要完毕的事。
给类和给对象加入职责有什么不同呢,前者的灵活性要差。假设须要加入的职责非常多,前者须要为每种情况都定义一个固定类,这里的每种情况指的是职责的排列组合,假如我要为一个原始类加入5个职责。则会出现5的阶乘种情况,不不过类爆炸带来的繁琐,执行时对职责的改动是随意的,这就使得编译时确定的类又要在执行时频繁改变,而直接往对象中加入职责则使的结构能够灵活多变,相同的情况,我只须要额外添加5个修饰类就能够完美解决类爆炸及执行时改变的情况,这就是上述装饰者模式定义所要表达的详细含义。
场景
产生类爆炸的原因非常多,并非每种情况都能够用装饰者模式来解决,该模式应用的典型场景需满足下面三点
1 原始组件(被装饰者)和装饰者符合逻辑上的修饰关系时
这个比較好理解,HeadFirst设计模式举了个为咖啡加入不同口味的配料,不同口味的配料相应一个装饰者,通常来讲。代码结构要反映出逻辑关系。不管是哪种口味的配料,他对咖啡对象确实产生了修饰关系,网上关于装饰者模式的举例大部分是关于饮食类,这是有道理的,不同口味或者配料相应不同的装饰者。这样是符合现实世界的逻辑关系的。
2 装饰者之间是能够独立存在的
还是继续咖啡的样例,每种口味之间都是独立关系,互相之间没有依赖关系。这个依据客户的需求。有人喜欢摩卡口味。有人喜欢奶油口味,这两种修饰者能够独立修饰咖啡的。当然有人喜欢摩卡奶油咖啡,这个不过修饰顺序的问题,不影响两种口味的独立性。假设修饰者之间不满足这样的独立性。使用装饰者模式是不合理得。
3 当无法确定原始组件被装饰的方式和时机时
一杯咖啡,依据客户需求的不同,加入不同口味的配料。客户会喜欢那种口味或者哪几种,事先是不确定的,咖啡店也不须要事先确定。他们会把不同口味的配料放在不同的机器中,随要随取。灵活应对需求,假设须要新口味。加入一个新机器就能够,扩展灵活,至少这些咖啡店的老板。都是懂装饰者模式的。
举例
装饰者模式的经典样例非常多,没有必要反复造轮子,就接着咖啡这个样例来说吧。
装饰者模式最核心的部分,就是从下图開始
一杯咖啡代表被装饰的组件component,咖啡与被辅料之间有两种关系,组合与继承。IS-A和Has-A的关系,HAS-A比較好理解,通过组合一个被装饰对象。能够使用被装饰对象的操作并进行装饰,IS-A则和通常情况有差别。辅料(Decorator)本质上并非咖啡。所以这里的继承关系并非类继承。而是接口(协议)继承。其含义就是经过辅料修饰后的咖啡仍然具有咖啡本身所含有的接口特性,也就是描写叙述和价格,不能把这个继承关系理解成调料也是一种咖啡,之所以使用继承的原因是能够方便灵活的在执行时动态加入职责,后面的測试例结果能够看出这一点。
和上述UML图对于的源代码例如以下
Cafe.java
package com.klpchan.example.cafe; public abstract class Cafe { public String getDescription() { return description; } public abstract float getPrice(); String description = "This is Cafe"; }
Decorator.java
package com.klpchan.example.cafe; public abstract class Decorator extends Cafe{ public Decorator(Cafe _cafe) { // TODO Auto-generated constructor stub this.cafe = _cafe; } @Override public String getDescription() { // TODO Auto-generated method stub return cafe.getDescription(); } @Override public float getPrice() { // TODO Auto-generated method stub return cafe.getPrice(); } Cafe cafe; }
咖啡作为被修饰者。含有非常多详细的子类,顾客选择时首先要选择详细的某种咖啡(详细component)。然后在为其选择口味(详细Decorator),本例选择脱脂(DECAF)和意式(Espresso)两种咖啡,第二图UML图诞生了
两种详细的咖啡是被修饰的详细类,各自己定义了咖啡的价格和说明,源代码例如以下
DeCaf.java
package com.klpchan.example.cafe; public class DeCaf extends Cafe{ public DeCaf() { // TODO Auto-generated constructor stub description = "This is DECAF cofe"; } @Override public float getPrice() { // TODO Auto-generated method stub return Constants.CAFE_DECAF_PRICE; } }
Espresso.java
package com.klpchan.example.cafe; public class Espresso extends Cafe{ public Espresso() { // TODO Auto-generated constructor stub description = "This is Espresso " ; } @Override public float getPrice() { // TODO Auto-generated method stub return Constants.CAFE_ESPRESSO_PRICE; } }
选择好咖啡后,用户開始选择口味配料(Decorator),这就是装饰模式的核心。本比如果有三种配料
摩卡(Mocha)、奶油(Milk)和巧克力(Chocolate),每种配料都有各自的价格,价格表详细例如以下
package com.klpchan.example.cafe; public class Constants { //脱脂和意式两种咖啡的基本价格 public static final float CAFE_DECAF_PRICE = 8; public static final float CAFE_ESPRESSO_PRICE = 9; //摩卡、牛奶、巧克力三种口味调料的价格 public static final float DECORATOR_MOCHA_PRICE = 0.5f; public static final float DECORATOR_MILK_PRICE = 0.4f; public static final float DECORATOR_CHOCOLATE_PRICE = 0.8f; }
通过加入详细的配料类来修饰咖啡,UML图例如以下
加入了三个详细的配料类摩卡、奶油和巧克力,源代码例如以下
MochaDecorator.java
package com.klpchan.example.cafe; public class MochaDecorator extends Decorator{ public MochaDecorator(Cafe _cafe) { super(_cafe); // TODO Auto-generated constructor stub } @Override public String getDescription() { // TODO Auto-generated method stub return super.getDescription() + " add mocha "; } @Override public float getPrice() { // TODO Auto-generated method stub return super.getPrice() + Constants.DECORATOR_MOCHA_PRICE; } }
MilkDecorator.java
package com.klpchan.example.cafe; public class MilkDecorator extends Decorator{ public MilkDecorator(Cafe _cafe) { super(_cafe); // TODO Auto-generated constructor stub } @Override public String getDescription() { // TODO Auto-generated method stub return super.getDescription() + " add milk "; } @Override public float getPrice() { // TODO Auto-generated method stub return super.getPrice() + Constants.DECORATOR_MILK_PRICE; } }
ChocolateDecorator.java
package com.klpchan.example.cafe; public class ChocolateDecorator extends Decorator{ public ChocolateDecorator(Cafe _cafe) { super(_cafe); // TODO Auto-generated constructor stub } @Override public String getDescription() { // TODO Auto-generated method stub return super.getDescription() + " add chocolate "; } @Override public float getPrice() { // TODO Auto-generated method stub return super.getPrice() + Constants.DECORATOR_CHOCOLATE_PRICE; } }
在每个详细的修饰类中,新方法使用了被修饰对象本身的方法并加上了新的特性,本例中是加上了说明和配料价格,新方法能够用在被修饰对象方法的前或者后,能够动态改变被修饰对象的状态和操作。被修饰对象(咖啡)并不须要知道修饰者(调料)究竟做了那些操作,这些都是透明的。
在客户文件写了个測试比例如以下
Cafe cafe = new DeCaf(); Cafe mochaChocCafe = new MochaDecorator(new ChocolateDecorator(cafe)); System.out.println(mochaChocCafe.getDescription() + " Price is " + mochaChocCafe.getPrice()); Cafe cafe2 = new Espresso(); Cafe milkChocMochaCafe = new MochaDecorator(new ChocolateDecorator(new MilkDecorator(cafe2))); System.out.println(milkChocMochaCafe.getDescription() + " Price is " + milkChocMochaCafe.getPrice());
能够看出对于一个选定的咖啡类,用户能够自由组合其装饰者,这就是较继承灵活的体现,执行结果例如以下
This is DECAF cofe add chocolate add mocha Price is 9.3
This is Espresso add milk add chocolate add mocha Price is 10.7
对于上述源代码中的价格表及測试源代码,能够看出两个订单的说明和价格均被正确改动完毕。
适用性
1 以动态透明的方式加入职责,动态前文已提及,透明是指被修饰者不依赖与修饰者,详细咖啡不依赖与调料,以脱脂咖啡为例,该类不须要关系是摩卡还是巧克力来修饰自己。更不关心这些修饰类怎样来修饰自己。代码结构中能够表现为咖啡类并不存在修饰类的引用。
2 处理能够撤销的职责。相当于上述过程的逆向project。将职责模块化,能够在执行时动态添加删除。
3 当无法使用继承来加入职责时。除了前文所述的因为职责过多引起的类爆炸。也可能是因为类定义被隐藏使得无法生成子类。
结构
这个和上述UML图的结构基本吻合。
Component 被修饰的组件抽象。本例中表示咖啡类,正如前文所述,装饰者和被装饰者须要同一时候继承这个抽象类,这样能够动态的为对象加入职责。
ConcreteComponent 详细的被修饰对象。本例中有脱脂咖啡和意式咖啡,详细组件不关心装饰类怎样装饰他们。
Decorator 装饰者抽象,本例中的调料基类,和组件是继承与组合的关系。装饰基类原封不动的调用被装饰者的操作。供详细修饰者重写。
ConcreteDecoratorA/B 详细装饰者。本例中的摩卡、牛奶、巧克力装饰类。能够改动被修饰者状态和行为。
效果
1 比静态继承更加灵活,前文已述。
2 避免在较高层次有较多特征,查看UML结构,组件和装饰者都有着较为简单的特征和操作。在使用该模式时。GOF建议为简单的类逐步加入功能而不是直接修饰一个复杂的类。由于逐步加入的功能能够组合出新的复杂功能。而直接扩展复杂的类easy暴露非常多与职责无关的细节,应该尽量保持组件的简洁性,组件应该做的是定义接口一类的工作,本例中的组件Cafe,只定义了主要的说明操作,由于装饰者也须要继承组件,在组件中定义过多且与职责没用的功能,会添加装饰者类的复杂程度,这段内容我理解的也不透彻,欢迎分享讨论~
3 Decorator与Component不一样。前文已述。被装饰的组件与装饰者的继承关系为接口继承,并不是类继承,这样做的目的是有利于多次修饰。
我们能够使用一个或多个装饰者来装饰一个对象,装饰后的对象仍然是component对象。
4 有很多小对象,这个能够參阅本文上述的測试程序,不同的装饰组合会产生的不同的对象,这个是装饰者模式的缺点。
5 为了解决扩展职责所带来的类爆炸和灵活性问题,该模式使用组合而非继承的格式,继承所产生的大量子类难以维护。更无法应对职责改变所引起的扩展性问题。
和其他模式的关系
适配器模式是改变一个对象的接口类型,使他成为可以满足与其他接口相匹配的要求。而装饰者模式是为对象动态透明的加入职责。
装饰者模式改变外壳,策略模式改变内核。事实上对象的外壳和内核是个相对的观点,类似于本例中的咖啡,咖啡的外壳改变就是前文所说口味的变化,口味变化并不影响咖啡的本质,意式咖啡怎样加糖也成为不了脱脂咖啡。而假设把意式咖啡在制作过程中的一些工艺替换了,则改变了内核,制作工艺的不同是意式咖啡和其他类型咖啡本质的差别,此时使用策略模式比較合理。这就是两者的差别。
应用场景
.NET框架中的装饰者模式应用,结构清晰不赘述了。
收尾
装饰者模式是较易理解的经常使用结构性模式,通过组合而非继承来解决原始类动态加入职责所引起的问题,本文偷个懒,直接改写了HEAD First设计模式的样例,此类样例解释的比較形象。对于一些较为晦涩的理论给出了自己的理解。模式这样的东西每一个人都会有不同的理解,细节上会有差异,欢迎分享交流,共同进步~
本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/4870776.html,如需转载请自行联系原作者