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.
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 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.
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.
In its base implementation, the Decorator pattern has four participants:
Recommended by LinkedIn
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:
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.
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.
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.
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.
The output of our application will be the following:
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
Relations with Other Patterns
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.