Mastering Decorator Design Pattern in C# for Beginners

Özkan ARDİL
7 min readDec 27, 2023

--

Using the decorator design pattern in C#, you can surround a component in another object without modifying the original code.

In this article we’ll;

  • Explore Decorator Design Pattern from class diagrams to real-world code examples,
  • Unveil its advantages,
  • Dissect the disadvantages,
  • Ensure it aligns with the SOLID principles.

By the end, you’ll be the master decorator design pattern, enhancing your software with finesse.

This pattern promotes the concept that your class should be closed for modification but open for extension.

In other words, you can add a functionality without disturbing the existing functionalities.

The concept is useful when you want to add some special functionality to a specific object instead of the whole class.

This pattern prefers object composition over inheritance.

Once you master this technique, you can add new responsibilities to an object without affecting the underlying classes.

Class Diagram

You will find the class diagram below.

Class diagram of Decorator Design Pattern

Elements

  • Concrete Class is our component which will be added new features.
  • Abstract Decorator is our Interface or abstract class that will enable the client to work with a single type. It is the structure from which our concrete decorator classes are derived.
  • Concrete Decorator is decorator class. It enables client adds new features to the concrete class without modifying base code.

Solution Explorer

You will find the high-level structure of the parts of the program.

Solution explorer of the Decorator Design Pattern sample project

Implementation

In this example, I have not modified the core MakeHouse() method. I have created two additional decorators, ConcreteDecoratorEx1 and ConcreteDecoratorEx2, to serve my needs, but I have kept the original structure intact.

First, I created the Component.cs and ConcreteComponent.cs classes.

 public abstract class Component
{
public abstract void MakeHouse();
}

ConcreteComponent.cs class derives from the abstract Component.cs class.

public class ConcreteComponent : Component
{
public override void MakeHouse()
{
Console.WriteLine("Original House is complete. It is closed for modification.");
}
}

Then I created a decorator abstract class and concrete decorator classes. Here is the AbstractDecorator.cs class code.

 public abstract class AbstractDecorator : Component
{
protected Component com;
public void SetTheComponent(Component c)
{
com = c;
}
public override void MakeHouse()
{
if (com != null)
{
com.MakeHouse();//Delegating the task
}
}
}

Then I created two concrete classes which derive from the AbstractDecorator.cs class

ConcreteDecoratorEx1.cs class implements the AbstractDecorator.cs class and adds a new feature without modifying the original code.

 public class ConcreteDecoratorEx1 : AbstractDecorator
{
public override void MakeHouse()
{
base.MakeHouse();
Console.WriteLine("***Using a decorator***");
//Decorating now.
AddFloor();
//You can put additional stuff as per your needs.
}
private void AddFloor()
{
Console.WriteLine("I am making an additional floor on top of it.");
}
}

ConcreteDecoratorEx2.cs class also implements the AbstractDecorator.cs class and adds an additional new feature without modifying the original code.

 public class ConcreteDecoratorEx2 : AbstractDecorator
{
public override void MakeHouse()
{
Console.WriteLine("");
base.MakeHouse();
Console.WriteLine("***Using another decorator***");
//Decorating now.
PaintTheHouse();
//You can add additional stuffs as per your need
}
private void PaintTheHouse()
{
Console.WriteLine("Now I am painting the house.");
}
}

So, I put them together within the program.cs class.

Console.WriteLine("***Decorator pattern Demo***\n");

ConcreteComponent cc = new ConcreteComponent();

ConcreteDecoratorEx1 decorator1 = new ConcreteDecoratorEx1();
decorator1.SetTheComponent(cc);
decorator1.MakeHouse();

ConcreteDecoratorEx2 decorator2 = new ConcreteDecoratorEx2();
//Adding results from decorator1
decorator2.SetTheComponent(decorator1);
decorator2.MakeHouse();

Console.ReadKey();

You’ll find the output below.

The output of the Decorator Design Pattern sample project

As you see in the example, in the case of inheritance, when a derived class inherits from a base class, it primarily acquires the base class’s behavior at the time of inheritance.

Despite various subclasses being able to extend the base class diversely, this binding occurs statically, determined during compile time.

However, employing composition, as demonstrated earlier, results in dynamic behavior. During the creation of a base or parent class, predicting the exact additional requirements clients might demand in the future phases becomes challenging.

With the restriction of not being able to alter existing code, object composition not only surpasses inheritance but also guarantees the preservation of the original architecture by preventing the introduction of bugs.

Lastly, adhering to a crucial design principle in this context emphasizes that classes should remain open for expansion while closed for direct modifications.

Source Code

You can access the source code of the project on my Design Patterns in C# GitHub repository.

If you can give the repository a star and share the article, you will support me in reaching more people.

Advantages of the Proxy Design Pattern

Safeguarding the Original Structure: The decorator pattern ensures that the existing framework remains unchanged, reducing the risk of introducing errors or bugs into the core system. This preservation of the base structure maintains stability and reliability.

Seamless Addition of Features: It facilitates the easy incorporation of new functionalities into an object without altering its fundamental structure. This flexibility allows for the dynamic enhancement of objects without the need for complex reconfigurations or modifications.

Incremental Development Approach: Embracing a step-by-step enhancement strategy, decorators enable gradual development rather than the necessity to plan and implement all functionalities at the outset. This incremental approach fosters adaptability, letting you add decorator objects gradually to meet evolving needs. Conversely, designing an intricate class initially and later expanding its functionalities becomes an arduous and less efficient process, lacking the flexibility inherent in the decorator pattern.

Disadvantages of the Proxy Design Pattern

Complexity Increase: As multiple decorators can be added to an object, the complexity of managing these decorators and understanding their interactions can grow. This can make the codebase harder to follow and maintain, particularly when dealing with numerous decorators.

Potential Overuse: If not used judiciously, the decorator pattern can lead to an excessive proliferation of small, specialized classes (decorators). This can make the system overly intricate, potentially impacting performance and readability.

Design Impact: While the pattern allows for adding functionalities dynamically, it might not be the best choice for every scenario. In some cases, using the decorator pattern extensively might negatively impact the overall design or make it harder to comprehend for other developers.

Runtime Overhead: Each decorator adds a layer of functionality, which can result in increased runtime overhead. This might be negligible for a few decorators but could become noticeable in systems with many decorators or when performance is critical.

Ordering Dependencies: The order in which decorators are applied can be crucial. Changing the sequence of decorators can affect the behavior of the object, potentially leading to unexpected results. Managing and controlling the order of decorators might require careful attention.

Understanding these disadvantages helps in making informed decisions about when and how to use the decorator pattern effectively in software design.

Decorator Design Pattern and SOLID Principles in C#

Single Responsibility Principle (SRP): This principle emphasizes that a class should have only one reason to change, meaning it should have a single, well-defined purpose. The Decorator pattern aligns with SRP by enabling the addition of responsibilities to an object without modifying its core structure. Each decorator handles a specific responsibility, adhering to the single responsibility principle.

Open/Closed Principle (OCP): This principle states that software entities (classes, modules, functions) should be open for extension but closed for modification. The Decorator pattern excels in complying with OCP as it allows for extending the behavior of objects by adding new decorators without altering their underlying code. It promotes the extension of functionality without changing the existing code base.

Liskov Substitution Principle (LSP): According to LSP, objects of a superclass should be replaceable with the objects of its subclasses without affecting the correctness of the program. The Decorator pattern maintains LSP compliance by ensuring that the decorated object (component) can be substituted with any other decorator or the base component without altering its behavior.

Interface Segregation Principle (ISP): ISP suggests that clients should not be forced to depend on interfaces they do not use. The Decorator pattern aligns well with ISP by allowing the creation of separate decorators for specific functionalities. Clients can choose and combine decorators according to their needs, without being dependent on the functionalities they don’t require.

Dependency Inversion Principle (DIP): DIP advocates that high-level modules/classes should not depend on low-level modules/classes but rather on abstractions. The Decorator pattern supports DIP by allowing the usage of abstract component classes/interfaces, making it easy to extend functionality depending on abstractions rather than concrete implementations.

In summary, the Decorator pattern demonstrates alignment with the SOLID principles by promoting flexibility, extensibility, and maintaining a clear separation of concerns, enabling developers to adhere to these principles while designing and extending the systems.

Conclusion

As we conclude our exploration of the Decorator Design Pattern in C#, it’s evident that it is a versatile and valuable tool in your software development arsenal.

With an understanding of class diagrams, code samples, advantages, and considerations, I hope you’ve gained the expertise to employ Decorator Design Pattern effectively.

By adhering to SOLID principles, you’ll ensure that your designs are scalable and maintainable.

Keep coding, keep designing, and keep mastering the art of C# development.

👏 If you found the content useful, I would appreciate it if you could support it with applause (you can also contribute with more than one applause by holding down the clap button for a long time).

⬇️ Check out my other articles.

💬 Let me know in the comment section what have I missed? Where I was wrong?

👨‍👦‍👦 You can also share this article with your friend. Argue a little about it. It is known that the truth is born in a dispute.

🙃 Stay determined, stay focused, and keep going.

Thanks for reading…

--

--

Özkan ARDİL

.NET C# JS and Angular dev with 8+ yrs exp, self-taught & passionate web developer. Sharing tips & experiences in C# and web dev.