Decorator Pattern – Streams in .NET
Andrea Angella
Technical Lead @ Redgate Software | ?Former Six-Times MVP on C# | ?? Grow Software Engineering Leaders | ?? Trainer, Mentor and Coach @ productivecsharp.com | ?? Host of The Productive C# Podcast | ?? Books Reader
Content originally published on my old blog in 2012.
The Decorator Pattern allows to attach additional responsibilities to an object dynamically. It is an alternative to subclassing for extending?behaviour.
Instead of creating a silly example of usage I decided to rely on one of the best example of implementation that is already available in the .NET Framework: the Streams.
The following class diagram is a restricted view of the Streams hierarchy in .NET:
Recognising the pattern is usually quite simple. When you see an object that inherit from a base class (Stream) and at the same time has a constructor that accept an element of the same base class (Stream), you?probably?came across to a Decorator.
The idea is that you can create an object that wraps an another object (of the same type) in order to provide new functionalities and the resulting object can be wrapped again creating a chain of objects that together provide a combination of functionalities. The beauty is that any single object is unaware that it is wrapped and this allows to create any combinations of them at runtime. If you consider the alternative of creating a single class for each combination of functionalities, the flexibility you get with the Decorator pattern is immense.
In the previous diagram there are three Decorators:
But what about the others classes?
FileStream, MemoryStream, NetworkStream does not have a constructor that takes a Stream. They simply are Components and not Decorators. This means that these objects can only be wrapped and cannot wrap other objects. These objects represent the end of the chain.
Yes, it seems quite complicated but it is not. Let’s make a concrete example.
Problem
Create a TCP client that allows you to send a message over the network to a TCP server. Before sending, the message will be buffered, compressed and then ciphered. The server will do the opposite operations in order to receive the message correctly.
Streams represents the best way to implement this scenario.
领英推荐
Solution
This is the code that creates a?TCP client and send the message over the network:
From the example, it should be easy to figure out how the wrapping process works. Wrapping is the way to add functionality in a dynamic way. If you don’t want to compress the data, you can simply remove the gzipStream wrapper. In addition, you can also change the order in which you want to execute the operations, simply wrapping in a different order. The decorator pattern provides you this flexibility.
The server side code is symmetric except that I decided to write the output to a file instead, using a FileStream.
Just for completeness this is the entry point of the program:
When you run the program the client send the message, the server receives and saves the message in a file that will be opened at the end.
The main Object Oriented Design principle respected by the decorator pattern is the?Open/Close Principle.
Software entities should be open for extension, but closed for modification.
What? How can you extend the system without changing it?
You can create a completely new class that inherit from Stream and add it into the chain in any position you want.
The streams hierarchy provided by .NET is open for extension but you don’t need to change the code of the framework to do this (it is closed for modification).
Senior Software Engineer( C# | GO | .Net | JS | React | Microservices | Blockchain)
1 年The Decorator Pattern allows Us to add or modify the behavior of an object dynamically at runtime, without changing the original object. It is useful when we need to add new features or behaviors to an object without modifying its existing code. ?? ??