說明:
裝飾模式是在不必改變原類文件和使用繼承的情況下,動態的擴展一個對象的功能。它是通過創建一個包裝對象,也就是裝飾來包裹真實的對象。
裝飾模式的特點;
(1) 裝飾對象和真實對象有相同的接口。這樣客戶端對象就可以以和真實對象相同的方式和裝飾對象交互。
(2) 裝飾對象包含一個真實對象的索引(reference)
(3) 裝飾對象接受所有的來自客戶端的請求。它把這些請求轉發給真實的對象。
(4) 裝飾對象可以在轉發這些請求以前或以後增加一些附加功能。這樣就確保了在運行時,不用修改給定對象的結構就可以在外部增加附加的功能。在面向對象的設計中,通常是通過繼承來實現對給定類的功能擴展。
下表格列舉了裝飾模式和繼承的不同:
裝飾模式 VS 繼承
裝飾模式 繼承
用來擴展特定對象的功能 用來擴展一類對象的功能
不需要子類 需要子類
動態地 靜態地
運行時分配職責 編譯時分派職責
防止由於子類而導致的複雜和混亂 導致很多子類產生,在一些場合,報漏類的層次
更多的靈活性 缺乏靈活性
對於一個給定的對象,同時可能有不同的裝飾對象,客戶端可以通過它的需要選擇合適的裝飾對象發送消息。 對於所有可能的聯合,客戶期望
很容易增加任何的 困難
例子:
讓我們重新返回我們在工廠方法和單例模式log實用工具上,我們的模式主要由Logger 接口和兩個它的實現類??FileLogger和ConsoleLogger??分別把信息出力到一個文件和屏幕中。另外,還有包括工廠方法的LoggerFactory類。
LoggerFactory沒有出現在下圖中,主要是因爲它和現在討論的例子沒有直接聯繫。
讓我們想象一些客戶端需要以超出Logger Utility現在所提供的新的方式出力信息,客戶端需要下面兩種特徵;
(1) 把出力的信息傳喚爲HTML文檔
(2) 對出力信息進行邏輯轉化的簡單加密,在面向對象的設計中,不改變現存的類的代碼,可以應用繼承來增加新的功能。例如,子類化現在的類重載它的方法來增加所需要的新功能。
應用繼承,我們要子類化FileLogger和ConsoleLogger類來增加新的功能,會有下面的一組新的子類:
子類 父類 功能
HTMLFileLogger FileLogger 轉化出力信息爲HTML文檔,並存入一個Log文件
HTMLConsLogger ConsoleLogger 轉化出力信息爲HTML文檔,並顯示在屏幕上
EncFileLogger FileLogger 加密出力信息,並存入一個Log文件
EncConsLogger ConsoleLogger 加密出力信息,並顯示在屏幕上
從類圖可以看到,爲了實現新的功能加入了一組新的子類。如果我們還有其他的Logger類型(例如:DBLogger出力信息到數據庫中),這樣會有更多子類。當一個新的特性需要被加入,子類的數量會有成倍數的增長,同時我們會有一個龐大的類層次。
裝飾模式使我們從這種情景中解脫出來,裝飾模式推薦通過對象的合成而不是繼承來包裝一個對象擴展它的功能。
應用裝飾模式,讓我們爲Logger Utility定義一個有下列特徵的默認根裝飾類LoggerDecorator:
(1) LoggerDecorator包括一個Logger實例的引用。這個引用指向它包含的Logger對象。
(2) LoggerDecorator實現Logger藉口、提供Log方法的基本的默認實現,他只是簡單的轉發調用給它包含的Logger 對象。每一個LoggerDecorator子類保證定義log方法。
Listing 19.1: LoggerDecorator Class
-
public class LoggerDecorator implements Logger {
-
Logger logger;
-
public LoggerDecorator(Logger inp_logger) {
-
logger = inp_logger;
-
}
-
public void log(String DataLine)
{
-
/*
-
Default implementation
-
to be overriden by subclasses.
-
*/
-
logger.log(DataLine);
-
}
-
}//end of class
每一個logger的裝飾定義log方法使很重要的,因爲裝飾對象必須提供和它包裝的對象相同的藉口。當客戶端創建一個裝飾類的實例,客戶端以與裝飾類交互方式和客戶端與擁有相同接口原對象的交互方式是一致的。
讓我們定義LoggerDecorator的兩個子類,HTMLLogger和EncryptLogger。 #p# 具體的Logger 裝飾類
HTMLLogger
HTMLLogger重載了log方法的默認實現。在log方法中,裝飾類把出力信息轉化爲HTML文檔,並且發送給可以出力的Logger實例。
Listing 19.2: HTMLLogger Class
-
public class HTMLLogger extends LoggerDecorator
{
-
public HTMLLogger(Logger inp_logger) {
-
super(inp_logger);
-
}
-
public void log(String DataLine)
{
-
/*
-
Added functionality
-
*/
-
DataLine = makeHTML(DataLine);
-
/*
-
Now forward the encrypted text to the FileLogger
-
for storage
-
*/
-
logger.log(DataLine);
-
}
-
public String makeHTML(String DataLine)
{
-
/*
-
Make it into an HTML document.
-
*/
-
DataLine = "" + """ + DataLine +
-
" + "";
-
return DataLine;
-
}
-
}//end of class
EncryptLogger
與HTMLLogger相似,EncryptLogger重載了log方法,在log方法中,EncryptLogger通過簡單的將字符位置向右轉移一位實現了加密邏輯,並且發送給可以出力的Logger實例。
Listing 19.3: EncryptLogger Class
-
public class EncryptLogger extends LoggerDecorator
{
-
public EncryptLogger(Logger inp_logger) {
-
super(inp_logger);
-
}
-
public void log(String DataLine)
{
-
/*
-
Added functionality
-
*/
-
DataLine = encrypt(DataLine);
-
/*
-
Now forward the encrypted text to the FileLogger
-
for storage
-
*/
-
logger.log(DataLine);
-
}
-
public String encrypt(String DataLine)
{
-
/*
-
Apply simple encryption by Transposition…
-
Shift all characters by one position.
-
*/
-
DataLine = DataLine.substring(DataLine.length() − 1) +
-
DataLine.substring(0, DataLine.length() − 1);
-
return DataLine;
-
}
-
}//end of class
爲了使用新設計裝飾對象,客戶端需要:
(1) 使用LoggerFactory工廠方法創建一個合適的Logger實例(FileLogger/ConsoleLogger)。
(2) 把第一步中創建的Logger實例作爲參數轉遞給新創建的合適的LoggerDecorator實例的構造函數。
(3) 調用LoggerDecorator實例上的方法,
Listing 19.4: Client DecoratorClient Class
-
class DecoratorClient {
-
public static void main(String[]
args) {
-
LoggerFactory factory = new LoggerFactory();
-
Logger logger = factory.getLogger();
-
HTMLLogger hLogger = new HTMLLogger(logger);
-
//the decorator object provides the same interface.
-
hLogger.log("A Message to Log");
-
EncryptLogger eLogger = new EncryptLogger(logger);
-
eLogger.log("A Message to Log");
-
}
-
}//End of class
增加新的信息出力類型
在Logging Utility實例中,應用裝飾模式對比使用繼承不會因爲類層次的增長而導致大量的子類,我們還有另外的Logger類型:DBLogger??出力信息到數據庫中。爲了將信息轉化HTML格式或在出力到數據庫以前對信息進行加密,客戶端只需遵從上面提到的步驟,因爲DBLogger是一種Logger類型,它可以作爲構造函數的參數傳遞給HTMLLogger或EncryptLogger中任何一個類。
增加新的裝飾
從例子中可以看到,LoggerDecorator實例包含了一個Logger類型了對象實例,在轉發請求給Logger對象實例以前或以後,增加新的功能。因爲LoggerDecorator類實現了Logger接口,LoggerDecorator實例或它的任何一個子類都可以作爲一個Logger類型。因此LoggerDecortator包含它的任何子類的一個實例,並且將請求轉發給它/。一般的一個裝飾對象可以包含另一個裝飾對象,並且可以向它轉發請求。通過這種方式,新的裝飾類,新的功能可以通過包裝現存的裝飾類來實現。 |