八:装饰模式
装饰 (Decorator) 模式 又叫包装 (Wrapper) 模式 ,它以对客户端透明的方式动态地扩展对象的功能,可以在不使用创造更多子类的情况下,将对象的功能加以扩展,是继承关系的一种替代。装饰模式使用原来被装饰类的一个子类的实例,把客户端的调用委派到被装饰类,其扩展是完全透明的。
涉及角色:
抽象构件 (Component) 角色:一个接口或者是抽象类,规范被装饰类,就是我们最核心、最原始的对象。在装饰模式中必然有一个被提取出来的最核心、最原始、最基本的接口或抽象类
具体构件 (Component) 角色:一个实现,被装饰的就是此角色
装饰 (Decorator) 角色:一般是抽象类,持有一个 Component 对象的实例
具体装饰 (ConcreteDecorator) 角色:具体的装饰类,既需要给被装饰类扩展什么功能。
再次说明,装饰模式是对继承的补充,继承不利于易维护、易扩展和易复用等的特性。继承是静态的给类增加功能,而装饰模式是动态的。
下面以一个例子来说明:
场景:大二的时候,学院坑人的要在暑假期间把每个人的历年成绩单寄给家长,并要家长签字后等假期结束上交
package com.co.patterns.decorator;
/**
* 成绩单
*/
public abstract class GradeReport {
// 展示成绩
public abstract void report();
// 家长签字
public abstract void sign(String name);
}
package com.co.patterns.decorator;
/**
* 我的成绩单
*/
public class SchoolGradeReport extends GradeReport {
public void report() {
System.out.println("尊敬的XXX家长:");
System.out.println(" ......");
System.out.println(" 高数 62 英语65 体育 98 电路 63");
System.out.println(" .......");
System.out.println(" 家长签名: ");
}
public void sign(String name) {
System.out.println("家长的名字:" + name);
}
}
package com.co.patterns.decorator;
/**
* 家长看了成绩单,并准备签字
*/
public class Client {
public static void main(String[] args) {
GradeReport report = new SchoolGradeReport();
// 看成绩单
report.report();
// 那么低的分数还有脸签字!
}
}
以上就是原始成绩单的样子以及客户端。但是我想,我要对成绩单装饰一下,然后让家长高兴的签字,我需要这样:
首先告诉家长,我们班里各科最高分:高数72 英语73 体育98 电路79;然后说我们班里总共就50个人,我之前是40几名,现在是20几名了(实际上有十几个人考试闹肚子了)。
我们用继承的方式,给成绩单增加一个子类,包装后:
package com.co.patterns.decorator;
/**
* 包装的成绩单
*/
public class NiceSchoolGradeReport extends SchoolGradeReport {
// 首先要定义你要美化的方法,先给老爸说学校最高成绩
private void reportHighScore() {
System.out.println("这次考试高数最高分是 72 英语73 体育 98 电路 78");
}
// 看完毕成绩单后,我再汇报排名情况
private void reportSort() {
System.out.println("我是排名第22名...");
}
// 由于汇报的内容已经发生变更,那所以要重写父类
public void report() {
this.reportHighScore(); // 先说最高成绩
super.report(); // 然后家长看成绩单
this.reportSort(); // 然后告诉家长学习学校排名
}
}
//美化过的成绩单拿过来
GradeReport report= new NiceSchoolGradeReport();
report.report();
report.sign("家长XXX");
但是现实情况是复杂的,假如家长看了最高成绩和我的成绩之后很高兴,不看排名了就直接签字或者是先看排名情况....,这时候该怎么办?再写子类?这样类就会激增,后期不好维护,还有设计时如果继承超过两层就可不太好了!装饰模式 就解决了这个问题。
package com.co.patterns.decorator;
/**
* 装饰角色
*/
public class Decorator extends GradeReport {
// 首先我要知道是那个成绩单
private GradeReport sr;
// 构造函数,传递成绩单过来
public Decorator(GradeReport sr) {
this.sr = sr;
}
// 成绩单还是要被看到的
public void report() {
this.sr.report();
}
// 看完毕还是要签名的
public void sign(String name) {
this.sr.sign(name);
}
}
package com.co.patterns.decorator;
/**
* 具体装饰角色
*/
public class HighScoreDecorator extends Decorator {
// 构造函数
public HighScoreDecorator(GradeReport sr) {
super(sr);
}
// 我要汇报最高成绩
private void reportHighScore() {
System.out.println("这次考试高数最高分是 72 英语73 体育 98 电路 78");
}
// 最高成绩在看成绩单前告诉他
public void report() {
this.reportHighScore();
super.report();
}
}
package com.co.patterns.decorator;
public class SortDecorator extends Decorator {
// 构造函数
public SortDecorator(GradeReport sr) {
super(sr);
}
// 告诉老爸学校的排名情况
private void reportSort() {
System.out.println("我的排名是22");
}
// 看完成绩单后再告诉他
public void report() {
super.report();
this.reportSort();
}
}
package com.co.patterns.decorator;
public class ClientWrapper {
public static void main(String[] args) {
//成绩单拿过来
GradeReport sr;
sr = new SchoolGradeReport(); //原装的成绩单
//加了最高分说明的成绩单
sr = new HighScoreDecorator(sr);
//又加了成绩排名的说明
sr = new SortDecorator(sr);
//看成绩单
sr.report();
System.out.println();
//一看,很开心,就签名了
sr.sign("家长XXX");
}
}
输出结果就这样了:
装饰模式的类图:
<!-- [if gte mso 9]><xml> <w:WordDocument> <w:View>Normal</w:View> <w:Zoom>0</w:Zoom> <w:PunctuationKerning/> <w:DrawingGridVerticalSpacing>7.8 磅</w:DrawingGridVerticalSpacing> <w:DisplayHorizontalDrawingGridEvery>0</w:DisplayHorizontalDrawingGridEvery> <w:DisplayVerticalDrawingGridEvery>2</w:DisplayVerticalDrawingGridEvery> <w:ValidateAgainstSchemas/> <w:SaveIfXMLInvalid>false</w:SaveIfXMLInvalid> <w:IgnoreMixedContent>false</w:IgnoreMixedContent> <w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText> <w:Compatibility> <w:SpaceForUL/> <w:BalanceSingleByteDoubleByteWidth/> <w:DoNotLeaveBackslashAlone/> <w:ULTrailSpace/> <w:DoNotExpandShiftReturn/> <w:AdjustLineHeightInTable/> <w:BreakWrappedTables/> <w:SnapToGridInCell/> <w:WrapTextWithPunct/> <w:UseAsianBreakRules/> <w:DontGrowAutofit/> <w:UseFELayout/> </w:Compatibility> <w:BrowserLevel>MicrosoftInternetExplorer4</w:BrowserLevel> </w:WordDocument> </xml><![endif]-->
<!-- [if gte mso 9]><xml> <w:LatentStyles DefLockedState="false" LatentStyleCount="156"> </w:LatentStyles> </xml><![endif]--><!-- [if gte mso 10]> <style> /* Style Definitions */ table.MsoNormalTable {mso-style-name:普通表格; mso-tstyle-rowband-size:0; mso-tstyle-colband-size:0; mso-style-noshow:yes; mso-style-parent:""; mso-padding-alt:0cm 5.4pt 0cm 5.4pt; mso-para-margin:0cm; mso-para-margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:10.0pt; font-family:"Times New Roman"; mso-ansi-language:#0400; mso-fareast-language:#0400; mso-bidi-language:#0400;} </style> <![endif]-->
应用场景:
(1) 需要扩展一个类得功能
(2) 动态地给一个对象增加功能,这些功能可以再动态地撤销
(3) 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使得继承关系变得不现实
由于装饰模式的灵活性,也意味着比继承更容易出错,比如说,不同的装饰类在排列组合时可能会产生一些不合理的组合等;还会长生比继承更多的对象。
同样的,也可以进行简化:如果只有一个具体构件角色而没有抽象构件角色,那么装饰类可以是具体构件角色的一个子类;如果只有一个具体装饰角色,则可以把装饰角色和具体装饰角色合并成一个。