譯-設計模式-結構模式之Decorator

翻譯整理自:https://refactoring.guru/design-patterns/decorator

更多請移步: 我的博客

目的

Decorator(裝飾器)是一個結構設計模式,可以讓你在封裝包涵對象原有行爲的基礎上增加新的行爲。

問題

你需要動態的添加或者移除一個對象的責任,但是你要做到和應用中其他代碼的兼容。

當你需要擴展一個類的行爲時繼承時第一個想到的處理方式。然而,繼承是靜態的。你不能夠增加一個新的類到程序中當它已經編譯或者執行完成。

解決辦法

裝飾器模式依賴一個叫做裝飾者(或者包裝者)的特別類。他們和被封裝的類擁有一樣的接口,所以客戶端代碼不會注意到你用封裝者替換了源對象。

所有的封裝者這都持有一個源對象實例的強引用。大多數包裝器使用傳入其構造函數的對象初始化該字段。

所以,該如何動態改變他的行爲呢?正如我提到的,封裝者何和目標對象擁有一樣的接口。當你調用裝飾者的方法時,他執行被封裝對象中同樣方法並且在返回的結果中添加一些東西。它也可以在原始方法之前調用,但這取決於業務邏輯。

這裏是有趣的一部分:你可以使用裝飾者封裝一個對象,然後再使用另外一個裝飾器封裝這個包裝結果,等等。最終的行爲結果是所有裝飾器和源對象組合得到的。

現實世界的類比

穿衣服就是使用裝飾者的例子。當你冷的時候,你用毛衣包裹自己。如果你還是冷,你可以在外邊套一個夾克。如果下雨了,你還可以再傳一件雨衣。

所有的服裝“擴展”自你基本的行爲,但不是你的一部分。因此,在你不需要他們的時候,可以輕鬆的移除它們。

結構

Structure

  1. Component爲封裝者何被封裝者聲明瞭一個通用的接口。

  2. Concrete Component是一個包涵基本行爲並可以被裝飾器修改的類。

  3. Base Decorator包涵一個被封裝對象的強引用域。這個域應該被聲明爲Component類型,以便支持Concrete Components 和 Decorators.Base Decorator將所有操作委託給被封裝對象。

  4. 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關鍵字來阻止未來對一個類的擴展。當處理這些代碼,進行擴展的唯一選項就是適用裝飾器模式。

如何實現

  1. 確保您的任務可以表示爲一個主要組件和幾個可選擴展。

  2. 創建Component(組件)接口,它需要描述該組件所有可被擴展的方法。

  3. 創建Concrete Component(具體組件)類並且實現業務邏輯。

  4. 創建Base Decorator(基礎裝飾)類。創建一個域來保存被封裝對象的強引用。該域應該是Component類型,這樣強引用就可以持有組件類和裝飾器的強引用(譯者注:即變量聲明爲接口類型,這樣可以持有所有Component的所有子類)。

  5. 確保所有子類實現了Component接口。

  6. 確保Base Decorator類的所有方法都將方法執行委託給了被包裝對象。它將允許Concrete Decorators(具體裝飾器)僅擴展一部分組件行爲,並且不需要修改其他行爲。

  7. 創建Concrete Decorators類,該類從Base Decorator擴展。

  8. 一個Concrete Decorator可以在調用被封裝對象相同方法前後執行它自己的的行爲(你可以僅僅只調用弗雷德方法,因爲它將最終調用封裝方法)。

  9. Client代碼必須負責配置包裝層。Client應該通過Component的接口和其他類一起工作,使裝飾器可以互換。

不必完全拘泥於以上步驟,一些情況下譯者認爲完全可以省略掉Base Decorator。

優缺點

  • 優點

    1. 比繼承靈活
    2. 允許在運行時添加和刪除行爲
    3. 通過使用多層封住,組合幾個額外的行爲。
    4. 可以組合多個單行爲實現使其實現更加複雜的行爲。
  • 缺點

    1. 配置一個多封裝對象是困難的。
    2. 導致很多小類。

和其他模式的關係

  • 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,它接受與當前類相同的類或接口的對象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章