Other Parts of This Series:

Decorator Design Pattern (Photo Credit: Refactoring.guru)
In this series, we try to explore software design patterns and principles. We will try to learn the well-known OOP design patterns one by one. In this part, we try to explore the decorator design pattern.
So let’s get started…
Story
Neela starts a bakery app called “Decorated Cake App.” Here he takes orders for different predefined delicious cakes. His app offers vanilla, chocolate, and other types of cake. Day by day, his app and cake become famous and popular. Now people propose and order cake with their preference and want cake decorated in a more elaborate way with extra functionality. So now, Neela needs to adjust his cake with extra functionality without breaking his existing system. As decorations are based on people’s choices, day by day more and more versatile preferences will come into the scenario, and Neela needs to adjust to those as well. Here Neela is in tension, wondering how he can achieve such cases in an optimized way.
Adapter Design Pattern
Definition:
Decorator is a structural design pattern that lets you attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors.
As the definition states, the decorator pattern helps us to adapt the new functionality along with the existing one. In this pattern a Wrapper object composites the Wrappe object and adds new functionality or decoration on top of the Wrappe object. So the question is, if any object adds extra functionality on top of the existing one, will it be considered a decorator pattern? The answer is no. When the Wrapper class and the Wrapped class both implement the same interface, then it will be called the decorator pattern.
Problem:
In the above story, Neela struggles with the additional features or decoration request of his existing cake. Though customization of cake will make his business stronger. Other example scenarios also highlight the attachment of additional functionality.
Other programming problems can be:
- Text Formatting in a Word Processor: Imagine you’re building a word processor application. You might have a base text object that represents plain text. You can use the decorator pattern to add additional formatting options like bold, italic, underline, etc. Each decorator adds a specific formatting feature without altering the original text object.
- Coffee Ordering System: In a coffee ordering system, you might have a base coffee class representing a simple cup of coffee. You can then use decorators to add extra ingredients like milk, sugar, whipped cream, flavor syrups, etc. Each decorator adds a new feature or modification to the original coffee order.
- Logging in a Web Application: Suppose you’re developing a web application and you want to add logging functionality to certain operations. Instead of directly modifying the code of these operations, you can use the decorator pattern to dynamically add logging behavior to them. This way, you can easily turn logging on or off as needed without changing the core functionality of the operations.
- Authentication in an API: In an API service, you might have a base authentication method. Using the decorator pattern, you can add additional layers of authentication such as OAuth, JWT, API key validation, etc. These decorators can be stacked on top of each other to provide multiple layers of security without modifying the original authentication method.
- Vehicle Customization in a Car Dealership System: In a car dealership system, you might have a base vehicle class representing a standard model of a car. Using the decorator pattern, you can add customization options like leather seats, sunroofs, alloy wheels, etc. Each decorator adds a different customization option to the base vehicle without altering its fundamental structure.
All of them need to extend the existing functionality without breaking the current one.
Solution:
From the above definition of the decorator pattern, it gives us the power of attaching or addingextra functionality on top of existing functionality without breaking the current system. As we can see from the story and above problem statements, the root problem or cause is the enhancements of functionality. So all the scenarios are the perfect example of the use cases of the decorator pattern. We can use or implement the decorator pattern as a solution to the problems stated in earlier sections.
For example, Neela can use a cake decorator to decorate his plain cake with customer preferences and so on…
UML Diagram: UML of Decorator Design Pattern (Photo Credit: Wikipedia)
Here in the UML diagram we can see that the Decorator and ConcreteComponent implement the same interface, Component. As mentioned earlier, as the decorator class and main component class implement the same interface, that’s why our system remains functional and they can be interchangeable. ConcreteDecorator is the concrete class with decorator functionality.
When To Use:
- Use the Decorator pattern when you need to be able to assign extra behaviors to objects at runtime without breaking the code that uses these objects.
- Use the pattern when it’s awkward or not possible to extend an object’s behavior using inheritance.
Implementation:
| |
Another C# implementation is available here.
Achieved Design Principles:
- Open/Closed Principle (OCP): This principle states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. In other words, you should be able to add new functionality or behavior to a system without altering the existing code.
- Single Responsibility Principle (SRP): Each decorator class has a single responsibility, which is to modify the behavior of the wrapped component. This helps in keeping classes focused on doing one thing and doing it well.
- Composition over Inheritance: Instead of relying on subclassing and inheritance to add functionality, the Decorator pattern emphasizes composition. It allows you to compose objects with different behaviors dynamically at runtime, promoting greater flexibility and avoiding the issues associated with deep class hierarchies.
- Interface Segregation Principle (ISP): Clients interact with the component interface and do not need to know about the decorators’ concrete implementations. This prevents clients from depending on methods they do not use, adhering to the ISP.
- Dependency Inversion Principle (DIP): By depending on abstractions (interfaces or abstract classes) rather than concrete classes, the Decorator pattern follows the DIP. This allows for easier substitution of components and promotes loosely coupled designs.
- Encapsulation: Each decorator encapsulates the behavior it adds to the wrapped component. This encapsulation ensures that changes in one decorator do not affect others or the base component, maintaining modularity and reducing unintended side effects.
- Flexibility and Extensibility: The Decorator pattern allows you to add or remove responsibilities (decorators) dynamically at runtime. This flexibility makes it easy to customize the behavior of objects without modifying their underlying classes, promoting extensibility.
- Maintainability: By separating concerns and keeping the core component and decorators decoupled, the Decorator pattern improves code maintainability. Changes in behavior can be made by adding or modifying decorators, rather than modifying existing classes, reducing the risk of introducing bugs.