Design Pattern: Decorator Pattern

Summary

Pattern NameDecorator Pattern
Pattern TypeStructural Pattern
ScopeObject
TaglineAdditional responsibility to individual objects without affecting other objects
Use casesWhen we want to attach additional responsibility/functionality
to individual objects and not to the class
Related PatternsAdapter
Proxy
Strategy
Difficulty LevelEasy
Implementations

Definition

In Decorator design pattern, our target is to add some new functionality without affecting the existing object.

Decorators can be considered as an alternative to sub-classing for introducing additional functionality.

Say, there is an existing class that is responsible for printing a UI element (like a Button or a Table). Later we need the ability to add a border or text color to that element.

We can add new properties and functions to that existing class and introduce those properties. But that will violate the Open-Close principle of the SOLID principle.

So we can use the Decorator pattern and introduce those new properties and functions. The decorator pattern will add one or more new Classes which will contain the existing functionality of the existing object and will also contain new functionalities. That way these new properties will not be added to every object. Only the new decorator class(es) objects will have these new functionalities.

Decorators add new functionality/responsibility to individual objects at runtime, not to the entire class.

You can think of the decorator as a new skin/layer over the existing object. The actual object exists as it is, by using Decorator pattern we are just adding classes that will wrap the existing class and add new functionalities on top of that.

Use Cases

Here are the use cases of Decorator pattern:

  • When we want to add new responsibility to an individual object dynamically (not to the entire class).
  • When we want to add new functionality to an object without affecting any other object of that class.
  • When creating a subclass is not possible or has side effects, and we want to add new functionality to objects.

Implementation

Decorator pattern implementation has 4 elements:

  1. Subject Interface [Actual Item Interface]: interface for the actual time classes. This will ensure the type of the actual class inside the decorator.
  2. Subject Concrete Class [Actual Item class]: item/subject class, to which we want to add the new responsibility.
  3. Decorator Interface: interface (abstract class) that holds the reference to the actual item object. This also ensures a common interface for all the decorators.
  4. Concrete Decorator Class: classes that introduce new responsibilities to the actual item objects.

A simple implementation of Decorator pattern will look like the diagram below.

Here are the steps you need to follow while implementing the Decorator design pattern.

  1. Create an Abstract class that implements the same interface as the actual object.
  2. This main Abstract class should set the actual object in the constructor.
  3. Introduce the decorator classes which extend the Main decorator abstract class.
  4. In the new decorator class call the constructor abstract class constructor so that the main class object can be set here.
  5. In the new decorator class call the functions from the actual class, and include additional functionality with that existing functionality.

Examples

The following examples demonstrate how can we implement Decorator pattern in different use cases.

Example #1: Simple Decorator

Let’s take an example that demonstrates a general decorator.

Subject [Interface and Class]

We need an interface for the subject and one or more concrete subject classes.

// Subject Interface

interface Subject

    operationOne()

    operationTwo()

end interface


// Concrete Subject

class ConcreteSubject implements Subject

    method operationOne()
        output "Performing Operation One(1) in Subject"
    end method

    method operationTwo()
        output "Performing Operation Two(2) in Subject"
    end method

end class

Decorator [Abstract and Concrete]

Then we can create the decorator, an abstract class, which implements the same Subject interface.

This decorator stores a subject object in a variable, which we can set using the constructor. For each method, we are calling the method from the subject.

In the decorator concrete class, we extend the Decorator abstract class, and in the methods, we are calling the method from the parent and also performing addition operations here.

// Decorator

abstract class Decorator implements Subject

    var subject: Subject

    constructor(subjectParam: Subject)
        subject = subjectParam
    end construcor

    method operationOne()
        this.subject.operationOne()
    end method

    method operationTwo()
        this.subject.operationTwo()
    end method

end class


// Concrete Decorator

class ConcreteDecorator extends Decorator

    constructor(subjectParam: Subject)
        super(subjectParam)
    end constructor

    method operationOne() {
        // perform some additional operation if required

        subject.operationOne()

        // perform some additional operation if required

        output "Performing additional operation in Concrete Decorator"
    end method

end class

Demo

In the client, we can call the Concrete Decorator and pass the subject concrete class to it. Then we can call the operations from the decorator object.

var someDecorator = new ConcreteDecorator(new ConcreteSubject())

someDecorator.operationOne();

Output

Performing Operation One(1) in Subject

Performing additional operation in Concrete Decorator

Example #2: Data Export

We are considering a data export operation for this example of Decorator pattern.

We have an existing class that can handle simple data export (fetching data and exporting in simple text format). Later we need to implement export as CSV, Excel, and JSON.

We can implement Decorator pattern to implement these new functionalities. Check the following implementation.

DataExport Interface

interface DataExport

   processData()

end interface

SimpleExport Class

class SimpleDataExport implements DataExport

   method processData() 
       // some operation to fetch data and handling
       
       output "Processing Data"
   end method

end class

Main Decorator Class

abstract class DataExportDecorator implements DataExport

   var dataExporter: DataExport

    constructor(dataExporterParam: DataExport)
        dataExporter = dataExporterParam
    end constructor

    method processData()
        dataExporter.processData()
    end method

end class

CSV Decorator Class

class CsvDataExportDecorator extends DataExportDecorator
    
    constructor(dataExporterParam: DataExport)
        // call parent constructor
        super(dataExporterParam)
    end constructor

    
    method processData() 
        dataExporter.processData()
        processCsv()
    end method

    method processCsv()
        // process data to CSV

        output "Processed data to CSV"
    end method

end class

Excel Decorator Class

class ExcelDataExportDecorator extends DataExportDecorator
    
    constructor(dataExporterParam: DataExport)
        // call parent constructor
        super(dataExporterParam)
    end constructor

    
    method processData() 
        dataExporter.processData()
        processExcel()
    end method

    method processExcel()
        // process data to Excel

        output "Processed data to Excel"
    end method

end class

JSON Decorator Class

class JsonDataExportDecorator extends DataExportDecorator
    
    constructor(dataExporterParam: DataExport)
        // call parent constructor
        super(dataExporterParam)
    end constructor

    
    method processData() 
        dataExporter.processData()
        processJson()
    end method

    method processJson()
        // process data to JSON

        output "Processed data to JSON"
    end method

end class

Usage

csvDataExport = new CsvDataExportDecorator(new SimpleDataExport())
csvDataExport.processData()

excelDataExport = new ExcelDataExportDecorator(new SimpleDataExport())
excelDataExport.processData()

jsonDataExport = new JsonDataExportDecorator(new SimpleDataExport())
jsonDataExport.processData()

Output

The output of the demo above will be like below.

Processing Data
Processed data to CSV

Processing Data
Processed data to Excel

Processing Data
Processed data to JSON

Code Implementations

Use the following links to check Decorator pattern implementation in specific programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.

Uncover the professional journey shaping your projects. Explore my profile on the About page.