## 連載:面向對象葵花寶典:思想、技巧與實踐(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模式結構】**

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);
}
}
~~~
**可以看到,使用了設計模式的方法后,打印業務的變化,可以通過類似數學上的排列組合已有的打印功能來完成,而不再需要新的打印類了。**
- 前言
- (1) - 程序設計思想的發展
- (2) - 面向對象語言發展歷史
- (3) - 面向過程 vs 面向對象
- (4) - 面向對象是瑞士軍刀還是一把錘子?
- (5) - 面向對象迷思:面向對象導致性能下降?
- (6) - 不要說你懂“類”
- (7) - “對象”新解
- (8) - “接口” 詳解
- (9) - “抽象類” 詳解
- (10) - “抽象” 詳解
- (11) - “封裝” 詳解
- (12) - “繼承” 詳解
- (13) - “多態” 詳解
- (14) - 面向對象開發技術流程
- (15) - 需求詳解
- (16) - 需求分析終極目的
- (17) - 需求分析518方法
- (18) - 用例分析
- (19) - 功能點提取
- (20) - 用例圖的陷阱
- (21) - SSD
- (22) - 領域模型
- (23) - 領域建模三字經
- (24) - 設計模型
- (25) - 類模型
- (26) - 類模型三板斧
- (27) - 動態模型設計
- (28) - 設計原則:內聚&耦合
- (29) - 高內聚低耦合
- (30) - SRP原則
- (31) - OCP原則
- (32) - LSP原則
- (33) - ISP原則
- (34) - DIP原則
- (35) - NOP原則
- (36) - 設計原則如何用?
- (37) - 設計模式:瑞士軍刀 or 錘子?
- (38) - 設計模式之道
- (39) - 設計原則 vs 設計模式
- (40) - DECORATOR模式
- (完)- 書籍已經出版