翻譯整理自:https://refactoring.guru/design-patterns/decorator
更多請移步: 我的博客
目的
Decorator(裝飾器)是一個結構設計模式,可以讓你在封裝包涵對象原有行爲的基礎上增加新的行爲。
問題
你需要動態的添加或者移除一個對象的責任,但是你要做到和應用中其他代碼的兼容。
當你需要擴展一個類的行爲時繼承時第一個想到的處理方式。然而,繼承是靜態的。你不能夠增加一個新的類到程序中當它已經編譯或者執行完成。
解決辦法
裝飾器模式依賴一個叫做裝飾者(或者包裝者)的特別類。他們和被封裝的類擁有一樣的接口,所以客戶端代碼不會注意到你用封裝者替換了源對象。
所有的封裝者這都持有一個源對象實例的強引用。大多數包裝器使用傳入其構造函數的對象初始化該字段。
所以,該如何動態改變他的行爲呢?正如我提到的,封裝者何和目標對象擁有一樣的接口。當你調用裝飾者的方法時,他執行被封裝對象中同樣方法並且在返回的結果中添加一些東西。它也可以在原始方法之前調用,但這取決於業務邏輯。
這裏是有趣的一部分:你可以使用裝飾者封裝一個對象,然後再使用另外一個裝飾器封裝這個包裝結果,等等。最終的行爲結果是所有裝飾器和源對象組合得到的。
現實世界的類比
穿衣服就是使用裝飾者的例子。當你冷的時候,你用毛衣包裹自己。如果你還是冷,你可以在外邊套一個夾克。如果下雨了,你還可以再傳一件雨衣。
所有的服裝“擴展”自你基本的行爲,但不是你的一部分。因此,在你不需要他們的時候,可以輕鬆的移除它們。
結構
Component爲封裝者何被封裝者聲明瞭一個通用的接口。
Concrete Component是一個包涵基本行爲並可以被裝飾器修改的類。
Base Decorator包涵一個被封裝對象的強引用域。這個域應該被聲明爲Component類型,以便支持Concrete Components 和 Decorators.Base Decorator將所有操作委託給被封裝對象。
Concrete Decorators包涵可以被動態添加的額外行爲。裝飾器可以在調用被封裝對象方法前後執行自己的行爲。
僞代碼
在這個例子中,裝飾器通過加密保護金融數據,對已經存在的代碼來講是透明的。應用對金融數據做了加密和壓縮裝飾,當我們從硬盤讀取數據時返回的是普通的數據,但是當我們寫會到磁盤時數據被加密和壓縮。
裝飾者和金融類都有一個相同的接口,使得它們對客戶端來講是通用的。
// Common interface for all components.
interface DataSource is
method writeData(data)
method readData():data
// One of the concrete components can act as a base layer.
class FileDataSource implements DataSource is
constructor FileDataSource(filename) { ... }
method writeData(data) is
Write data to file.
method readData():data is
Read data from file.
// All other concrete components may act as wrappers.
class DataSourceDecorator implements DataSource is
protected field wrappee: DataSource
constructor DataEncyptionDecorator(source: DataSource) is
wrappee = source
method writeData(data) is
wrappee.writeData(data)
method readData():data is
return wrappee.readData()
// Concrete Decorators extend the functionality of a component they wrap.
class EncyptionDecorator extends DataSourceDecorator is
method writeData(data) is
Encrypt passed data.
Pass the compressed data to wrappee's writeData() method.
method readData():data is
Get the data from wrappee's readData() method.
Decrypt and return that data.
// You can wrap objects in several layers of decorators.
class CompressionDecorator extends DataSourceDecorator is
method writeData(data) is
Compress passed data
Pass the compressed data to wrappee's writeData() method.
method readData():data is
Get the data from wrappee's readData() method.
Uncompress and return that data.
// Option 1. A simple example of decorator assembly.
class Application is
method dumbUsageExample() is
source = new FileDataSource('somefile.dat')
source.writeData(salaryRecords)
// a file with plain data
source = new CompressionDecorator(source)
source.writeData(salaryRecords)
// compressed file
source = new EncyptionDecorator(source)
source.writeData(salaryRecords)
// compressed and encrypted file
// Option 2. Client code, which uses an external data source. SalaryManager
// neither knows not cares about data storage specifics. It receives already
// configured data source.
class SalaryManager is
field source: DataSource
constructor SalaryManager(source: DataSource) { ... }
method load() is
return source.readData()
method save() is
source.writeData(salaryRecords)
// ...Other useful methods...
// Application can assemble objects with a different set of functionality using
// the same decorators at run time, depending on the configuration
// or environment.
class ApplicationConfigurator is
method configurationExample() is
source = new FileDataSource("salary.dat");
if (enabledEncryption)
source = new EncyptionDecorator(source)
if (enabledCompression)
source = new CompressionDecorator(source)
logger = new SalaryLogger(source)
salary = logger.load();
// ...Rest of an application code.
適用性
當你需要動態賦予某個對象行爲並且不需要破壞這個對象的代碼。
裝飾器模式允許給某個對象動態的賦予新的行爲,而且對客戶端代碼是隱式的。對象可以同時封裝多個wrapper(譯者注:就像同時穿了背心、襯衣和西裝),結果是所有封裝的堆疊結果。當不可能活着不合適通過繼承來擴展對象的行爲。
許多編程語言都有final
關鍵字來阻止未來對一個類的擴展。當處理這些代碼,進行擴展的唯一選項就是適用裝飾器模式。
如何實現
確保您的任務可以表示爲一個主要組件和幾個可選擴展。
創建Component(組件)接口,它需要描述該組件所有可被擴展的方法。
創建Concrete Component(具體組件)類並且實現業務邏輯。
創建Base Decorator(基礎裝飾)類。創建一個域來保存被封裝對象的強引用。該域應該是Component類型,這樣強引用就可以持有組件類和裝飾器的強引用(譯者注:即變量聲明爲接口類型,這樣可以持有所有Component的所有子類)。
確保所有子類實現了Component接口。
確保Base Decorator類的所有方法都將方法執行委託給了被包裝對象。它將允許Concrete Decorators(具體裝飾器)僅擴展一部分組件行爲,並且不需要修改其他行爲。
創建Concrete Decorators類,該類從Base Decorator擴展。
一個Concrete Decorator可以在調用被封裝對象相同方法前後執行它自己的的行爲(你可以僅僅只調用弗雷德方法,因爲它將最終調用封裝方法)。
Client代碼必須負責配置包裝層。Client應該通過Component的接口和其他類一起工作,使裝飾器可以互換。
不必完全拘泥於以上步驟,一些情況下譯者認爲完全可以省略掉Base Decorator。
優缺點
優點
- 比繼承靈活
- 允許在運行時添加和刪除行爲
- 通過使用多層封住,組合幾個額外的行爲。
- 可以組合多個單行爲實現使其實現更加複雜的行爲。
缺點
- 配置一個多封裝對象是困難的。
- 導致很多小類。
和其他模式的關係
Adapter提供不同的接口來達到目的。Proxy提供相同的接口。Decorator提供增強的接口。
Adapter意味着改變一個存在對象的接口。Decorator在不改變原有接口的情況下增強另外一個對象。Decorator對應用來講比Adapter更加透明。因此,Decorator支持遞歸組合,這對於純Adapter是不可能的。
Chain of Responsibility(責任鏈)和Decorator具有非常普通的類機構。它們都依賴於一系列對象的遞歸組合來執行。但是它們也有幾個關鍵的不同點。
Chain of Responsibility的處理者可以執行隨意的行爲操作,處理者之間相互獨立。它們可以隨意的終端下一步的調用。另一方面,各種Decorator擴展一個特別的行爲並且假設保持接口一致。並且,Decorator不允許隨意中斷執行鏈。
Composite和Decorator擁有類似的結構圖,因爲它們都依賴遞歸組合來組織一個對象開閉的數量。
裝飾器可以看作只有一個組件的退化組合。然而,Decorator向對象增加了額外的責任,而Composite只是對其子類執行相同的行爲的“summs up”。但是它們也可以協作:Composite可以使用Decorator來改變樹組件的行爲。
大量使用Composite和Decorator模式的設計通常可以從Prototype中受益。它允許克隆複雜的結構,而不是從頭重新構建它們。
裝飾器可以讓您更改對象的皮膚。策略讓你改變勇氣。
Decorator和Proxy有相似的結構,但是目的不同。兩種模式都建立在將工作委託給其他對象的組合原則上。然而,Proxy自己管理他持有服務對象的生命週期,而Decorator結構由客戶端控制。
Java中模式的使用
用例:Decorator在Java中是十分標準的,尤其是和流相關的代碼。
這有幾個Java核心庫中使用Decorator的例子:
java.io.InputStream,OutputStream,Reader和Writer的所有子類都有接受它們自己類型的構造方法。
java.util.Collections,方法checkedXXX(),synchronizedXXX()和unmodifiableXXX()。
javax.servlet.http.HttpServletRequestWrapper和HttpServletResponseWrapper。
鑑定:可以通過創建方法或構造函數來識別Decorator,它接受與當前類相同的類或接口的對象。