Decorator Pattern in C#

Decorator Pattern in C#

Originally posted here

The Decorator is a structural design pattern that lets us attach new behaviours to objects by placing these objects inside special wrappers. These wrappers add the desired behaviour without modifying the original code.

The Decorator is a handy tool when we have some object that we want to enhance with additional behaviours. Still, we are either unwilling or unable to change its internal workings. With this pattern, we can wrap the object in a specialized wrapper and implement the functionality in the wrapper.

You can find the example code of this post, on GitHub

Conceptualizing the Problem

Imagine that we have a notification library which lets us notify a user about deployment events.

The initial version of the library is based on a Notifier class that has a few fields such as a list of email addresses, a constructor and a single Notify method. The Notify method accepts a message argument from the client and sends it to a list of emails, provided via the Notifier's constructor. Some other application acts as a client and will configure the Notifier once, and then use it each time something important happens during the deployment workflow.

No alt text provided for this image

The library worked perfectly for some time, but then disaster struck. During a nightly release, the build failed, and the emails were sent but there wasn't anyone to address the issue. After all, most people don't check their emails in the middle of the night.

Now the users require much more than just email notifications. Many of them would like to receive an SMS notification about critical issues. Others would like Microsoft Teams notifications, and the DevOps teams would love to get Slack notifications for some reason or another.

No alt text provided for this image

No problem there! We can extend the Notifier class and add the additional notification methods into new subclasses. Now the client can instantiate the appropriate notification class and use it for further notification.

But then someone asked the million-dollar question. "Why can't you use several notifications at once? If the boat is sinking you'd probably want to be informed through every channel".

Keeping with our previous solution, we can address the problem by extending the Notifier class with special subclasses that combine several notification methods in one class. Time for some simple math now. Each notification method can either exist or not exist in a subclass. Thus there are two states for the existence of a notification method. We currently have 3 different notification methods. So there are 2 to the power of 3 different combinations of subclasses or 8 classes. If we add another notification method we will have 2 to the power of 4 different subclasses, or 16 different classes altogether. It becomes apparent that this approach will bloat the code exponentially. 

Extending a class is the first thing that comes to mind when we need to alter an object's behaviour. However, there are some issues with Inheritance.

  • Inheritance is static. We can't alter the behaviour of an existing object at runtime. We can only replace the instance with another that's created by a different subclass.
  • Subclasses can have just one parent class. In C#, inheritance doesn't let a class inherit behaviours from multiple classes simultaneously.

One of the ways to overcome these caveats is by using Aggregation or Composition instead of Inheritance. Both of these alternatives are almost identical. An instance has a reference to another instance and delegates some work, whereas, with inheritance, the instance itself executes that work, inheriting the behaviour from its parent.

With this new approach, we can easily substitute the linked object with another changing the behaviour of the container at runtime. Aggregation and Composition are key techniques behind many design patterns, including the Decorator.

A Decorator, also known as a Wrapper, can be linked with some target object. The wrapper can delegate all requests it receives to the target. However, the wrapper can alter the result by either processing the request before it is sent to the target or altering the response after the target returns a result.

Structuring the Decorator Pattern

The following diagram demonstrates how the Decorator pattern works.

No alt text provided for this image

  1. The application makes a request and the decorator class intercepts it.
  2. The decorator class can pre-process the request before passing it to the wrapped class.
  3. The wrapped class performs its functionality, as usual, unaware of the decorator class.
  4. The decorator class can post-process the response before passing it to the application.
  5. The decorator returns the result to the original caller.

In its base implementation, the Decorator pattern has four participants:

  • Component: The Component declares the common interfaces for both wrappers and wrapped objects.
  • Concrete Components: The Concrete Component is the class of objects that will be wrapped. It defines the original behaviour that can be altered by the decorators.
  • Base Decorator: The Base Decorator references the wrapped object. The base decorator delegates all operations to the wrapped object.
  • Concrete Decorators: The Concrete Decorator defines additional behaviours that can be added to Concrete Components dynamically. 
  • Client: The Client can wrap components in multiple layers of decorators, as long as it works with the objects via a shared interface.

No alt text provided for this image

To demonstrate how the Decorator pattern works, we will open a fancy farm-to-fork restaurant.

The idea behind the restaurant is that make dishes from ingredients directly acquired from the producer. This means that some dishes may be marked as sold out since the farm can produce only so many ingredients. 

To start, we will implement our Component participant, which is our abstract Dish class:

No alt text provided for this image

We also need a couple of ConcreteComponent participant classes representing the individual dishes our restaurant can serve. These classes only care about the ingredients of a dish and not the number of dishes available. This is the responsibility of the Decorator.

No alt text provided for this image
No alt text provided for this image

Now, we need to keep track of whether a dish is available. To do this we will first implement an AbstractDecorator class which is our Decorator participant.

No alt text provided for this image

Finally, we need a ConcreteDecorator participant, to keep track of how many of the dishes have been ordered. This is the role of the AvailabilityDecorator.

No alt text provided for this image

The last step is to set up the Main method. First, we will define a set of dishes, and then decorate them so they can keep track of their availability. Finally, we will order the dishes.

No alt text provided for this image

The output of our application will be the following:

No alt text provided for this image

The Problem of Fat Decorators

Decorators are all about composability. This means that the reused class is not inherited but wrapped by decorator classes. This comes with a caveat. The decorator does not inherit the public interface of the reused class, but needs to explicitly implement every method of the interface. Creating and maintaining all of these methods can add quite an overhead. 

While there are some development tools like ReSharper that can help with handling all of those methods, the responsibility of maintenance still lies on the developer.

Pros and Cons of the Decorator Pattern

No alt text provided for this image

Relations with Other Patterns

  • The Adapter and Decorator work in a similar way. However, the Adapter changes the interface of an existing object, while the Decorator enhances an object without changing its interface. In addition, the Decorator supports recursive composition, which is impossible when using the Adapter. Finally, the Adapter provides a different interface for the wrapped object, while the Decorator provides it with an enhanced interface.
  • The Chain of Responsibility and Decorator have similar class structures. Both interfaces rely on recursive composition. However, there are some differences. The CoR handler can execute arbitrary operations without dependence on other handlers in the chain. On the other hand, a Decorator can extend the object's behaviour while keeping it consistent with the base interface. Moreover, decorators aren't allowed to break the flow of the request.
  • The Composite and Decorator patterns have a similar structure since both rely on recursive composition. A Decorator is like a Composite but only has one child component. Also, the Decorator adds additional responsibilities to the wrapped object, while the Composite just sums up its children's results.
  • The Decorator and Proxy have similar structures, but very different intents. Both patterns are built on the composition principle. However, a Proxy usually manages the life cycle of its service object on its own, whereas the composition of the Decorator is always controlled by the client.

Final Thoughts

In this article, we have discussed what is the Decorator pattern, when to use it and what are the pros and cons of using this design pattern. We then examined what is a fat decorator and how the Decorator pattern relates to other classic design patterns. 

It's worth noting that the Decorator pattern, along with the rest of the design patterns presented by the Gang of Four, is not a panacea or a be-all-end-all solution when designing an application. Once again it's up to the engineers to consider when to use a specific pattern. After all these patterns are useful when used as a precision tool, not a sledgehammer.

To view or add a comment, sign in

More articles by Konstantinos Kalafatis

  • Premises of Software Architecture

    This post is the first of a series of posts around Software Architecture. In this series we are going to discuss what…

  • Adapter Pattern in C#

    Originally posted here The Adapter is a structural design pattern that allows objects with incompatible interfaces to…

  • Visitor Pattern in C#

    The Visitor is a behavioural design pattern that lets us separate algorithms from the entities on which they operate…

  • Observer Pattern in C#

    Originally posted here The Observer is a behavioural design pattern that lets us define a subscription mechanism to…

  • Mediator Pattern in C#

    Originally posted here The Mediator is a behavioural design pattern that lets us reduce chaotic dependencies between…

  • Factory Method Pattern in C#

    Originally posted here The Factory Method is a creational design pattern that allows the abstraction of object…

  • Singleton Pattern in C#

    Originally posted here The Singleton is a creational design pattern that lets us ensure that a class has only one…

  • Composite Pattern in C#

    Originally posted here The Composite is a structural design pattern that allows us to compose objects into tree…

  • Builder Pattern In C#

    Originally posted here The Builder is a creational design pattern that allows us to construct complex objects step by…

    1 Comment
  • Application Caching Strategies

    Originally posted here Most, if not all, developers are at least somewhat familiar with the concept of caching. After…

Insights from the community

Others also viewed

Explore topics