装饰模式
一、概述
定义: 动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更灵活。
原理:增加一个修饰类包裹原来的类,包裹的方式一般是通过在将原来的对象作为修饰类的构造函数的参数。装饰类实现新的功能,但是,在不需要用到新功能的地方,它可以直接调用原来的类中的方法。因为修饰类和原来的类有相同的接口。
装饰模式以对客户端透明的方式动态的给一个对象附加上更多的责任。换言之客户端并不会觉的对象在装饰前和装饰后有什么区别。 装饰模式可以在不创造更多的子类的模式下,将对象的功能加以扩展。
二、适用性:
- 需要扩展一个类的功能,或给一个类增加附加责任。
- 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
- 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实。
三、引入例子 装饰成绩单骗取家长签字
对成绩单进行装饰,骗取家长签字。
个成绩单的类图
成绩单的抽象类
public abstract class SchoolReport {
// 成绩单的主要展示的就是你的成绩情况
public abstract void report();
// 成绩单要家长签字,这个是最要命的
public abstract void sign(String name);
}
四年级的成绩单实现类
/**
* 四年级的成绩单
*/
public class FouthGradeSchoolReport extends SchoolReport {
// 我的成绩单
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);
}
}
老爸开始看最真实的成绩单,
/**
* 老爸看原始成绩单
*/
public class Father {
public static void main(String[] args) {
// 成绩单拿过来
SchoolReport sr = new FouthGradeSchoolReport();
// 看成绩单
sr.report();
// 签名?休想!
}
}
结果就是老爸不给签名
为了骗取签名,我没有直接把成绩单交给家长,而是在交给他之前做了点技术工作,我要把成绩单封装一下,封装分类两步走:
第一步:跟老爸说各个科目的最高分,语文最高是75,数学是78,自然是80,然老爸觉的我成绩与最高分数相差不多,(实情是基本上都集中在70分以上,我这60多分基本上还是垫底的角色)
第二步:在老爸看成绩单后,告诉他我是全班排名第38名,(实情是有将近十个同学退学了!这个情况我是不说的。反正成绩单上没有写总共有多少同学,排名第几名等等)
使用继承的方法
我想这是你最容易想到的类图,通过直接增加了一个子类,重写report方法,很容易的解决了这个问题,
public class SugarFouthGradeSchoolReport extends FouthGradeSchoolReport {
// 首先要定义你要美化的方法,先给老爸说学校最高成绩
private void reportHighScore() {
System.out.println("这次考试语文最高是75,数学是78,自然是80");
}
// 在老爸看完毕成绩单后,我再汇报学校的排名情况
private void reportSort() {
System.out.println("我是排名第38名...");
}
// 由于汇报的内容已经发生变更,那所以要重写父类
@Override
public void report() {
this.reportHighScore(); // 先说最高成绩
super.report(); // 然后老爸看成绩单
this.reportSort(); //然后告诉老爸学习学校排名
}
}
Father类稍做修改就可以看到美化后的成绩单
public class Father {
public static void main(String[] args) {
// 美化过的成绩单拿过来
SchoolReport sr = new SugarFouthGradeSchoolReport();
// 看成绩单
sr.report();
// 然后老爸,一看,很开心,就签名了
sr.sign("老三"); // 获得签名
}
}
获得签名
存在的问题
通过继承确实能够解决这个问题,老爸看成绩单很开心,然后就给签字了,但是现实的情况很复杂的,可能老爸听我汇报最高成绩后,就直接乐开花了,直接签名了,后面的排名就没必要了,或者老爸要先听排名情况,那怎么办?继续扩展类?你能扩展多少个类?这还是一个比较简单的场景,一旦需要装饰的条件非常的多,比如20个,你还通过继承来解决,你想想的子类有多少个?你是不是马上就要崩溃了!
好,你也看到通过继承情况确实出现了问题,类爆炸,类的数量激增,
装饰模式来解决这些问题
增加一个抽象类和两个实现类,其中Decorator的作用是封装SchoolReport类,Decorator抽象类的目的很简单,就是要让子类来对封装SchoolReport的子类,怎么封装?重写report方法。,先调用具体装饰类的装饰方法reportHighScore,然后再调用具体构件的方法。
Decorator
/**
* 装饰类,我要把我的成绩单装饰一下
*/
public abstract class Decorator extends SchoolReport {
// 首先我要知道是那个成绩单
private SchoolReport sr;
// 构造函数,传递成绩单过来
public Decorator(SchoolReport sr) {
this.sr = sr;
}
// 成绩单还是要被看到的
public void report() {
this.sr.report();
}
// 看完毕还是要签名的
public void sign(String name) {
this.sr.sign(name);
}
}
HighScoreDecorator实现类:
/**
* 我要把我学校的最高成绩告诉老爸
*/
public class HighScoreDecorator extends Decorator {
// 构造函数
public HighScoreDecorator(SchoolReport sr) {
super(sr);
}
// 我要汇报最高成绩
private void reportHighScore() {
System.out.println("这次考试语文最高是75,数学是78,自然是80");
}
// 最高成绩我要做老爸看成绩单前告诉他,否则等他一看,就抡起笤帚有揍我,我那还有机会说呀
@Override
public void report() {
this.reportHighScore();
super.report();
}
}
SortDecorator实现类:
/**
* 排名的情况汇报
*/
public class SortDecorator extends Decorator {
// 构造函数
public SortDecorator(SchoolReport sr) {
super(sr);
}
// 告诉老爸学校的排名情况
private void reportSort() {
System.out.println("我是排名第38名...");
}
// 老爸看完成绩单后再告诉他,加强作用
@Override
public void report() {
super.report();
this.reportSort();
}
}
老爸一看成绩单
public class Father {
public static void main(String[] args) {
// 成绩单拿过来
SchoolReport sr;
sr = new FouthGradeSchoolReport(); // 原装的成绩单
// 加了最高分说明的成绩单
sr = new HighScoreDecorator(sr);
// 又加了成绩排名的说明
sr = new SortDecorator(sr);
// 看成绩单
sr.report();
// 然后老爸,一看,很开心,就签名了
sr.sign("老三"); //获得签名
}
}
四、模式解读
4.1、类图
4.2、角色
1) 抽象构建角色(Component):给出一个抽象的接口,以规范准备接受附加责任的对象。
2) 具体的构建角色(ConcreteComponent):定义一个将要接受附加责任的类。
3) 装饰角色(Docorator):持有一个抽象构建(Component)角色的引用,并定义一个与抽象构件一致的接口。
4) 具体的装饰角色(ConcreteDecorator):负责给构建对象“贴上”附加的责任。
4.3、一般代码
1) 抽象的构建接口:
public interface Component {
public void doSomething();
}
2) 具体的构建角色:
public class ConcreteComponent implements Component {
@Override
public void doSomething() {
System.out.println("功能A");
}
}
3) 装饰角色:
public class Decorate implements Component {
private Component component;
public Decorate(Component component) {
this.component = component;
}
@Override
public void doSomething() {
component.doSomething();
}
}
4) 具体装饰角色1:
public class ConcreteDecorate1 extends Decorate {
public ConcreteDecorate1(Component component) {
super(component);
}
@Override
public void doSomething() {
super.doSomething();
this.doAnotherDosomething();
}
private void doAnotherDosomething() {
System.out.println("功能B");
}
}
5) 具体装饰角色2:
public class ConcreteDecorate2 extends Decorate
{
public ConcreteDecorate2(Component component)
{
super(component);
}
@Override
public void doSomething()
{
super.doSomething();
this.doAnotherDosomething();
}
private void doAnotherDosomething()
{
System.out.println("功能C");
}
}
6) 客户端
public class Client {
public static void main(String[] args) {
Component component = new ConcreteDecorate1(new ConcreteDecorate1(
new ConcreteComponent()));
component.doSomething();
}
}
五 、装饰模式的特点:
1) 装饰对象和真实对象具有相同的接口,这样客户端对象就可以以真实对象的相同的方式和装饰对象交互。
2) 装饰对象包含一个真实对象的引用(reference).
3) 装饰对象接受所有来自客户端的请求,它把这些请求转发给真实的对象。
4) 装饰对象可以在转发这些请求以前或者以后增加一些附加的功能。这样就能确保在运行时,不用修改给定对象结构就可以在外部增加附加的功能。在面向对象的程序设计中,通常是使用继承的关系来扩展给定类的功能。
六、装饰模式与类继承的区别:
1)装饰模式是一种动态行为,对已经存在类进行随意组合,而类的继承是一种静态的行为,一个类定义成什么样的,该类的对象便具有什么样的功能,无法动态的改变。
2)装饰模式扩展的是对象的功能,不需要增加类的数量,而类继承扩展是类的功能,在继承的关系中,如果我们想增加一个对象的功能,我们只能通过继承关系,在子类中增加两个方法。
3)装饰模式是在不改变原类文件和使用继承的情况下,动态的扩展一个对象的功能,它是通过创建一个包装对象,也就是装饰来包裹真是的对象。
4)装饰模式把对客户端的调用委派给被装饰的类,装饰模式的关键在于这种扩展完全透明的。
七、使用装饰模式的优点和缺点
优点:
装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错。
缺点:
由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像
八、例子2 打印票据
http://www.blogjava.net/flustar/archive/2007/11/28/decorator.html
例如,我们要为超市的收银台设计一个打印票据的程序,有的需要打印票据的头信息,有的需要打印票据的页脚信息,有的只需要打印票据的内容。如果针对每一种情况都修改一次程序,势必会很麻烦。这时我们可以考虑使用Decorator模式。其结构类图如下:
Component
abstract class Component {
abstract public void printTicket();
}
SalesTicket
class SalesTicket extends Component {
public void printTicket() {
System.out.println("打印出salesTicket的内容");
}
}
TicketDecorator
abstract class TicketDecorator extends Component {
private Component myTrailer;
public TicketDecorator(Component myComponent) {
myTrailer = myComponent;
}
public void callTrailer() {
if (myTrailer != null)
myTrailer.printTicket();
}
}
Header
class Header extends TicketDecorator {
public Header(Component myComponent) {
super(myComponent);
}
public void printTicket() {
System.out.println("打印salesTicket的头信息");
super.callTrailer();
}
}
Footer
class Footer extends TicketDecorator {
public Footer(Component myComponent) {
super(myComponent);
}
public void printTicket() {
super.callTrailer();
System.out.println("打印salesTicket的页脚信息");
}
}
Client
public class Client {
public static void main(String[] args) {
System.out.println("====================================");
new Header(new Footer(new SalesTicket())).printTicket();
System.out.println("====================================");
new Footer(new Header(new SalesTicket())).printTicket();
System.out.println("====================================");
}
}
运行结果
==================================== 打印salesTicket的头信息 打印出salesTicket的内容 打印salesTicket的页脚信息 ==================================== 打印salesTicket的头信息 打印出salesTicket的内容 打印salesTicket的页脚信息 ==================================== |
九、java I/O中的装饰模式的应用
1)抽象构建角色(Component):给出一个抽象的接口,以规范准备接受附加责任的对象。相当于I/O流里面InputStream/OutputStream和Reader/Writer。
2)具体的构建角色(ConcreteComponent):定义一个将要接受附加责任的类。相当于I/O里面的FileOutputStream和FileInputStream。
3)装饰角色(Docorator):持有一个抽象构建(Component)角色的引用,并定义一个与抽象构件一致的接口。相当于I/O里面的FilerOutputStream和FilterInputStream。
4)具体的装饰角色(ConcreteDecorator):负责给构建对象“贴上”附加的责任。相当于I/O流里面的BufferedOutputStream和BufferedInputStream以及DataOutputStream和DataInputSrtream。
参考
设计模式之禅
http://www.cnblogs.com/wangjq/archive/2012/07/03/2574755.html
http://www.blogjava.net/flustar/archive/2007/11/28/decorator.html
http://www.51cto.com/specbook/11/11650.htm