(40) – DECORATOR模式
最后更新于:2022-04-01 07:30:12
## 连载:面向对象葵花宝典:思想、技巧与实践(40) - DECORATOR模式
**掌握了设计模式之道后,我们将以全新的方法来理解设计模式,这个方法更简单、更直观,不信?看几个样例就知道了**
=====================================================================
**DECORATOR模式(以设计模式之道来理解)**
**【业务】**
假设你进入了一个信息安全管理非常严格的公司,这家公司不允许员工自行打印文档,所有的文档打印都需要交给文档打印系统统一管理。文档打印系统会记录每次打印的时间、内容、打印人员。。。。。。等等,以便后续出现问题的时候进行追查。
由于公司有很多的部门,每个部门的安全要求并不完全一样,同时每个部门关于文档打印也有自己的一些规定。
我们的任务就是要开发一套能够支持整个公司文档打印需求的系统。
**【发现变化】**
文档打印系统面对的变化主要体现在:文档打印要求是变化的,不同的部门有不同的要求,同一个部门也可能修改自己的打印需求。
例如:
A部门是一个战略规划的部门,里面的资料都非常重要,打印的时候需要在页眉位置打印“绝密”,在页脚的位置打印“密级申明”,同时要加上“绝密文档”的水印;
B部门是内部培训部门,打印培训材料的时候需要在页眉位置打印“内部公开”,但不需要密级申明,同时加上“培训资料”的水印
C部门是对外宣传部门,打印宣传材料的时候只需要加上“公司logo”的水印;
**【传统方法】**
传统方法使用类继承来封装打印请求,为每个部门创建一个打印的子类。详细示例代码如下:
PrintTask.java -- 文档打印系统开发小组负责维护
~~~
package com.oo.designpattern.decorator;
/**
* 打印任务类
*
*/
abstract public class PrintTask {
abstract public void print(String text);
}
~~~
SecretPrint.java -- 文档打印系统开发小组负责维护:
~~~
package com.oo.designpattern.decorator;
/**
* 绝密文档的打印
*
*/
public class SecretPrint extends PrintTask{
@Override
public void print(String text) {
Printer.printHeader("绝密");
Printer.printText(text);
Printer.printFooter("本文包含绝密信息,请勿泄露!");
Printer.printTextWaterMark("绝密文档");
}
}
~~~
InternalPrint.java -- 文档打印系统开发小组负责维护:
~~~
package com.oo.designpattern.decorator;
/**
* 内部公开的文档打印
*
*/
public class InternalPrint extends PrintTask {
@Override
public void print(String text) {
Printer.printHeader("内部公开");
Printer.printText(text);
Printer.printTextWaterMark("培训资料");
}
}
~~~
PublicPrint.java -- 文档打印系统开发小组负责维护:
~~~
package com.oo.designpattern.decorator;
import java.awt.Image;
/**
* 对外宣传的文档打印
*
*/
public class PublicPrint extends PrintTask {
private Image _logo;
@Override
public void print(String text) {
Printer.printText(text);
Printer.printImgWaterMark(_logo);
}
}
~~~
文档打印系统实现如下:
PrintServer -- 文档打印系统开发小组负责维护
~~~
package com.oo.designpattern.decorator;
/**
* 文档打印系统
*
*/
public class PrintServer {
/**
* 执行打印任务
* @param task
* @param text
*/
public static void executePrintTask(PrintTask task, String text){
log();
task.print(text);
audit();
}
/**
* 记录日志
*/
private static void log(){
//省略具体实现代码
}
/**
* 记录审计相关信息
*/
private static void audit(){
//省略具体实现代码
}
}
~~~
定义好不同的打印任务后,每个部门根据自己的需要,选择不同的任务发给文档打印系统。
例如,A部门的打印处理如下:
SecretDepartment.java -- A部门负责维护
~~~
package com.oo.designpattern.decorator;
/**
* A部门的打印处理
*
*/
public class SecretDepartment {
public void print(String text){
PrintTask task = new SecretPrint();
//PrintServer即“文档打印系统”
PrintServer.executePrintTask(task, text);
}
}
~~~
传统方法使用类继承来封装变化的打印需求,当面对变化时,存在如下问题:
1)新增部门的时候,需要文档打印系统提供一个新的打印任务类,将导致出现大量的***Print类;
例如:新建了一个D部门,D部门只需要打印纯文本即可,那么已有的SecretPrint、InternalPrint、PublicPrint类都无法满足需求,必须新增一个PurePrint的类;
2)某个部门的打印需求变更的时候,需要改变已有的***Print类;
例如:C部门希望在对外宣传材料的页眉上打印公司名称,则需要修改PublicPrint类。
**【设计模式方法】**
设计模式封装变化的方法就是Decorator模式。Decorator模式定义如下:
“动态的给一个对象添加一些额外的职责”
《设计模式》一书中关于Decorator模式的描述并不很直观,我理解Decorator模式为“通过聚合的方式将动态变化的职责组合起来”。
我们详细看看Decorator模式是如何封装变化的。
首先,将变化的职责封装为独立的类。传统方式实现中,不同的职责是对应不同的函数调用,而设计模式中,不同的职责是不同的类;
其次,通过聚合将变化的职责组合起来。传统方式中,不同职责的组合是通过在一个函数中写多行代码来体现的,而设计模式中,通过对象的聚合将不同职责组合起来。
**【Decorator模式结构】**
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-20_569f5ccb46f4b.jpg)
Component:定义一个对象接口(对应结构图中的operation函数),可以给这些对象动态添加职责
ConcreteComponent:定义一个对象,这个对象是实际的Component,将被Decorator修饰
Decorator:定义修饰对象的接口,Decorator实现的关键在于聚合了一个Component对象
ConcreteDecorator:具体的修饰对象
**【代码实现】**
使用Decorator设计模式实现的文档打印系统代码如下:
*********************类设计*****************************
PrintComponent.java -- 文档打印系统开发小组负责维护
~~~
package com.oo.designpattern.decorator2;
/**
* 打印组件的父类
*
*/
abstract public class PrintComponent {
abstract public void print();
}
~~~
PrintDecorator.java -- 文档打印系统开发小组负责维护
~~~
package com.oo.designpattern.decorator2;
/**
* 修饰的打印任务,对应Decorator模式中的Decorator
* Decorator可以聚合ConcreteComponent或者其他Decorator
* 这样可以使得打印任务能够嵌套执行下去,直到最后完成所有打印任务
*
*/
public abstract class PrintDecorator extends PrintComponent {
abstract public void print();
}
~~~
TextComponent.java -- 文档打印系统开发小组负责维护
~~~
package com.oo.designpattern.decorator2;
import com.oo.designpattern.decorator.Printer;
/**
* 文本打印,对应Decorator模式中的ConcreteComponent
* 打印任务到ConcreteComponent就算真正完成了
*
*/
public class TextComponent extends PrintComponent {
private String _text;
TextComponent(String text){
this._text = text;
}
@Override
public void print() {
Printer.printText(this._text);
}
}
~~~
HeaderDecorator.java -- 文档打印系统开发小组负责维护
~~~
package com.oo.designpattern.decorator2;
import com.oo.designpattern.decorator.Printer;
/**
* 页眉打印
*
*/
public class HeaderDecorator extends PrintDecorator {
private PrintComponent _comp; //被修饰的打印组件
private String _text; //需要打印的页眉内容
/**
* 初始化的时候,必须包含其它打印组件comp,这是实现Decorator模式的前提
* 同时也需要指定当前组件所需的参数,不能在print函数的参数中指定,
* 因为每个Decorator所需的参数是不一样的
* @param comp
* @param text
*/
HeaderDecorator(PrintComponent comp, String text) {
this._comp = comp;
this._text = text;
}
/**
* 打印
*/
@Override
public void print() {
//打印的时候将当前Decorator和被修饰的Component分开,这是Decorator模式的关键
Printer.printHeader(_text); //打印页眉
//_comp本身如果是Decorator,就会嵌套打印下去
//_comp本身如果不是Decorator,而是ConcreteComponent,则打印任务到此结束
_comp.print();
}
}
~~~
FooterDecorator.java
~~~
package com.oo.designpattern.decorator2;
import com.oo.designpattern.decorator.Printer;
/**
* 页脚打印,和页眉打印类似,此处省略相同的注释代码
*
*/
public class FooterDecorator extends PrintDecorator {
private PrintComponent _comp;
private String _text;
FooterDecorator(PrintComponent comp, String text) {
this._comp = comp;
this._text = text;
}
/**
* 打印
*/
@Override
public void print() {
Printer.printFooter(_text); //打印页脚
_comp.print();
}
}
~~~
TextWatermarkDecorator.java
~~~
package com.oo.designpattern.decorator2;
import com.oo.designpattern.decorator.Printer;
/**
* 文本水印打印,和页眉打印类似,此处省略相同的注释代码
*
*/
public class TextWatermarkDecorator extends PrintDecorator{
private PrintComponent _comp;
private String _text;
TextWatermarkDecorator(PrintComponent comp, String text) {
this._comp = comp;
this._text = text;
}
/**
* 打印
*/
@Override
public void print() {
Printer.printTextWaterMark(_text); //打印文本水印
_comp.print();
}
}
~~~
ImgWatermarkDecorator.java
~~~
package com.oo.designpattern.decorator2;
import java.awt.Image;
import com.oo.designpattern.decorator.Printer;
/**
* 图片水印打印,和页眉打印类似,此处省略相同的注释代码
*
*/
public class ImgWatermarkDecorator extends PrintDecorator {
private PrintComponent _comp;
private static Image _logo; //图片水印只能是公司logo
ImgWatermarkDecorator(PrintComponent comp) {
this._comp = comp;
}
/**
* 打印
*/
@Override
public void print() {
Printer.printImgWaterMark(ImgWatermarkDecorator._logo); //打印图片水印
_comp.print();
}
}
~~~
PrintServer.java
~~~
package com.oo.designpattern.decorator2;
public class PrintServer {
/**
* 执行打印任务
* @param comp
*/
public static void executePrintTask(PrintComponent comp){
log();
comp.print();
audit();
}
/**
* 记录日志
*/
private static void log(){
//省略具体实现代码
}
/**
* 记录审计相关信息
*/
private static void audit(){
//省略具体实现代码
}
}
~~~
*********************类使用*****************************
A部门的打印处理如下(**如下代码请仔细阅读,特别是注释部分**):
SecretDepartment.java -- A部门负责维护
~~~
package com.oo.designpattern.decorator2;
/**
* A部门的打印处理,注意与传统方法中的SecretDepartment类对比
*
*/
public class SecretDepartment {
/**
* 打印任务1,对应传统方式的SecretePrint类
* @param text
*/
public void print(String text){
/**
* 使用Decorator设计模式后,打印任务不再是一个单独的类SecretPrint类,
* 而是通过将多个打印项目聚合成一个打印任务
*/
PrintComponent textComp = new TextComponent(text);
//注意header聚合了textComp
PrintDecorator header = new HeaderDecorator(textComp, "绝密");
//注意footer聚合了header,而不是textComp,这样就能够嵌套执行下去
PrintDecorator footer = new FooterDecorator(header, "本文包含绝密信息,请勿泄露!");
//注意watermark聚合了footer,而不是textComp,这样就能够嵌套执行下去
PrintDecorator watermark = new TextWatermarkDecorator(footer, "绝密文档");
//PrintServer即“文档打印系统”,与传统的PrintServer相比,这里不需要知道打印的text内容
//text内容已经封装到TextComponent中去了(对应代码行14行)
PrintServer.executePrintTask(watermark); //注意这里传递给打印系统的是最后一个Decorator对象watermark
}
/**
* A部门的第二个打印任务,将文本水印改为图片水印,并且不再打印页脚
* @param text
*/
public void print2(String text){
/**
* 新增打印任务,无需文档管理系统增加新的类,只要A部门自己修改代码即可
*/
PrintComponent textComp = new TextComponent(text);
//注意header聚合了textComp
PrintDecorator header = new HeaderDecorator(textComp, "绝密");
//注意watermark聚合了header,而不是textComp,这样就能够嵌套执行下去
PrintDecorator watermark = new ImgWatermarkDecorator(header);
PrintServer.executePrintTask(watermark);
}
}
~~~
**可以看到,使用了设计模式的方法后,打印业务的变化,可以通过类似数学上的排列组合已有的打印功能来完成,而不再需要新的打印类了。**