Decorator design pattern

Adnan taşdemir
8 min readMar 29, 2023

--

Decorator design pattern, is a structural design pattern that enables adding new behaviors to objects by placing them inside special wrapper objects that contain the object’s existing behaviors. In other words, it is a design pattern that allows dynamically adding additional functionality to an object.

Decorator design pattern

The Decorator design pattern consists of five main components. Figure 1 illustrates the main components of the Decorator design pattern.

  1. The Component class defines a common interface for both wrappers and wrapped objects. It provides an interface for the actual object to which responsibilities can be dynamically added.
  2. The ConcreteComponent class is a class of the wrapped objects. It defines the basic behavior that can be dynamically added by the Decorator.
  3. The Decorator class implements both the Component interface and holds a reference to an object instance of the Component type within itself. Therefore, as seen in the UML diagram, there is an Aggregation relationship between the Decorator and Component.
  4. The ConcreteDecorator is responsible for adding new responsibilities to the components. It defines the additional behaviors that can be dynamically added.
  5. The Client can wrap the components in multiple decorator layers.
Figure 1, UML class diagram of decorator design pattern.

The clothing example shown in Figure 2 is also an example of using the Decorator pattern. When you are cold, you wear a sweater. If you are still cold, you can wrap yourself with a jacket. If it’s raining, you can wear a raincoat. All of these clothes expand your basic behavior and you can easily remove any clothes when you don’t need them.

Figure 2, real life example related to the Decorator design pattern

The applicability of the Decorator design pattern

  1. One scenario where the Decorator pattern is useful is when you need to add new behavior to an object at runtime. For example, in a web application, you may want to add new features to a user interface without changing the underlying code. By using the Decorator pattern, you can wrap the existing code in a Decorator class that adds the new functionality.
  2. Another scenario where the Decorator pattern is useful is when you need to add features to an object in a specific order. For example, you may have a series of filters that need to be applied to an image in a specific sequence. By using the Decorator pattern, you can create a chain of Decorator classes that apply the filters in the required order.
  3. The Decorator pattern is also useful when you want to add functionality to an object that may be withdrawn later. By wrapping the object in a Decorator class, you can easily remove the added functionality by simply removing the Decorator class.
  4. Furthermore, the Decorator pattern is beneficial when you want to avoid subclassing an object repeatedly to add functionality. This can lead to a complex class hierarchy that is difficult to manage and understand. With the Decorator pattern, you can add functionality to an object without the need to create new subclasses.
  5. In conclusion, the Decorator pattern is highly applicable in many software development scenarios where you need to add new behavior to an object dynamically, in a specific order, or without affecting other objects in the same class. By using the Decorator pattern, you can create flexible and reusable software components that are easy to manage and extend.

Decorator design pattern application steps

  1. Identify the base object: The first step in applying the Decorator pattern is to identify the base object that you want to add functionality to. This object should have a clear and simple interface that can be easily extended.
  2. Define a common interface: Next, you need to define a common interface for all the Decorator classes that will wrap the base object. This interface should be compatible with the base object’s interface and provide a common way to access the additional functionality.
  3. Create the base object: Once you have identified the base object and defined the common interface, you can create the base object. This object will be wrapped by the Decorator classes, and its functionality can be extended dynamically.
  4. Create the Decorator classes: The next step is to create the Decorator classes that will wrap the base object. These classes should implement the common interface defined in step 2 and add new functionality to the base object. Each Decorator class should wrap the base object and delegate calls to it as necessary.
  5. Chain the Decorator classes: To add multiple Decorator classes to the base object, you need to chain them together. Each Decorator class should have a reference to the Decorator class that it is wrapping. This chain of Decorator classes provides a flexible and extensible way to add functionality to the base object.
  6. Use the Decorator classes: Finally, you can use the Decorator classes to add new functionality to the base object dynamically. Simply create an instance of the base object and wrap it with one or more Decorator classes. The Decorator classes will add the new functionality to the base object without changing its structure or behavior.

Decorator design pattern advantages and disadvantages

  • Advantages:
  1. Flexible extension: The Decorator pattern allows you to add new functionality to an object dynamically, without changing its structure or behavior. This makes it easy to extend and modify the functionality of an object, without affecting other parts of the system.
  2. Easy to maintain: The Decorator pattern follows the principle of single responsibility, which means that each Decorator class is responsible for adding a specific functionality. This makes it easy to maintain and modify the functionality of the system, as each Decorator class can be modified or replaced independently.
  3. Open-Closed principle: The Decorator pattern follows the Open-Closed principle, which means that the classes are open for extension but closed for modification. This makes it easy to add new functionality to an object without modifying its existing code.
  4. Reusability: The Decorator pattern promotes code reuse by allowing you to reuse existing Decorator classes to add new functionality to other objects.
  • Disadvantages:
  1. Complexity: The Decorator pattern can lead to a complex code structure, especially when multiple Decorator classes are used. This can make the code difficult to understand and maintain, especially for developers who are not familiar with the pattern.
  2. Performance overhead: The Decorator pattern adds a performance overhead, as each Decorator class needs to perform its own operations before delegating the call to the next Decorator in the chain. This can impact the performance of the system, especially when a large number of Decorator classes are used.
  3. Dependency injection: The Decorator pattern can be difficult to implement in systems that use dependency injection frameworks, as the framework needs to be aware of the Decorator pattern and how to create the Decorator objects.

An example of the decorator design pattern

Let there be a business called a digital store. This store sells (Samsung, Huawei, Xiaomi, Iphone) phones and other components such as (headphones, usb cable, charger and shatterproof glass). Let a system be designed for this store. The first design that comes to mind for the classes of this system is shown in figure 3.

Figure 3, UML class diagram of the system

The Phone class is an abstract class. All subclasses inherit the Phone class. The “description” variable is set in each subclass and contains the phone’s description. Since the “cost( )” method is an abstract method, it must also be present in subclasses and returns the price of the phone. This build still only includes phones. But there are other components in this store as well. The newly created system is shown in Figure 4.

Figure 4, UML class diagram of the new system

The customer may purchase the phone or purchase the phone with one component or more than one component. Thus, the “cost( )” method now returns the final price, adding both the phone price and the price of the other received components. However, when the number of components in the store, the number of phone brands and phone versions are too many, there will be an inexplicable need for subclasses. This is also now called a class explosion. Another proposed system is shown in Figure 5 below to solve this problem.

Figure 5, UML class diagram of the proposed system

In this structure, subclasses now call the methods they inherit. So the “cost( )” method returns the prices of the requested devices. However, there are several problems that can be encountered in this structure. Price changes for components are forced to replace existing code. When new components are purchased, changes must be made to the codes. What to do when the customer wants to buy two earphones?

All these problems can be easily solved with the Decorator design pattern logic. The “cost( )” method in the Huawei object shown in option a in Figure 8 returns the price of the phone. In Figure 8, it is assumed that one charge will be taken in option b. In this case, an object called charger was created and this object encapsulated the Huawei object. The “cost( )” method on the Charger object returns the price of its charge plus the price of the phone.

In Figure 6, in option c, a headset will also be taken, and in this case, an object called Headphone was created. This object encapsulated around previous objects. Returns the price of the “cost( )” method charge in the Headphone object.

Figure 6, Decorator design pattern logic

Figure 7, shows how the price of the devices is calculated. The “cost( )” on the Headphone object is called first. This method calls “cost( )” on the Charger object. Again this method calls “cost( )” on the Huawei object. The “cost( )” method on the Huawei object returns 1800. The “cost( )” method on the Charger object returns 1800 + 149.99. The “cost( )” in the Headphone object returns 1949.99 + 99.99 ie 2049.98.

Figure 7, encapsulated objects

Figure 8, shows the UML class diagram designed for the Digital Store. The Phone class is an abstract class and contains public methods. The Samsung, Huawei, Xiaomi and iPhone classes inherit the Phone class and can also define new methods of their own. The Decorator class also inherits the Phone class. An Aggregation relationship exists between the Decorator and the Phone class. Headphone, Usb, Charger and Screen protector classes define extra behaviors that can be added dynamically.

Figure 8, Decorator design pattern UML class diagram for this problem

--

--

No responses yet