Basic Decorator Pattern
(Click on the diagram to open in a separate window)
The name of the pattern is very descriptive. It involves a main class that does most of the work, represented by the ConcreteComponent class above, and one or more Decorator classes that inherits from the same base class as ConcreteComponent (the abstract Component class in the diagram above), and also holds onto a reference to a Component class.
The Decorator classes delegate most of the real work to their Component class instance, but "decorate" its functionality by tacking on their own behavior.
Note that in the diagram above the abstract DecoratorComponent doesn't add a lot of value - it defines a way to pass the reference to the Component class to the decorators, and nothing else. When there's only a single Decorator you can simplify the diagram like so:
Simplified Decorator Pattern
It's not terribly easy to find real-life examples of the Decorator Pattern. One of the examples given by the GoF is also the example given in this MSDN article that describes the Decorator Pattern, which is the Stream family of classes:
In this example the CryptoStream class inherits from the abstract Stream class and is also given a Stream object reference, overrides the Write method, encrypts the bytes and then calls the Write method on the contained stream to write the encrypted bytes. In fact, if you use Reflector on the .NET Framework CryptoStream class you can see that it has actually been implemented using this pattern. The CryptoStream constructor takes a Stream reference:
And the Write method encrypts the buffer before writing it to the stream:
The Decorator Pattern can provide a very elegant means of composing the behaviors of objects at run time. In the example above, because CryptoStream is passed an abstract Stream object base class reference, it can operate on any type of stream: a FileStream, MemoryStream or any type of stream that you may create in the future. And because CryptoStream itself derives from Stream, it can be passed to other decorators. So if in the future a CompressionStream class were created, you could compose a FileStream that is encrypted and compressed, or a MemoryStream that is compressed by not encrypted. All this can be done dynamically, at runtime, rather than at compile time, as would be the case if you were to attempt this via inheritance.
Pros and Cons of the Decorator Pattern, according to the GoF:
Pros:
The Decorator classes delegate most of the real work to their Component class instance, but "decorate" its functionality by tacking on their own behavior.
Note that in the diagram above the abstract DecoratorComponent doesn't add a lot of value - it defines a way to pass the reference to the Component class to the decorators, and nothing else. When there's only a single Decorator you can simplify the diagram like so:
Simplified Decorator Pattern
In fact even when there are multiple Decorator classes the base Decorator class is often omitted, as is the case in the CryptoStream example from the .NET Framework, which is shown below.
The GoF book says that another name for this pattern is the Wrapper Pattern, but in my opionion "wrapper" is a loose term that applies to a number of patterns in which one object delegates most of its behavior to another, "contained" object. In particular, I'd say "wrapper" applies to the Decorator, Fascade and Adapter patterns, but not to the Proxy pattern because in that case the object you are delegating to is not conceived of as being "contained in" the proxy.
The GoF book says that another name for this pattern is the Wrapper Pattern, but in my opionion "wrapper" is a loose term that applies to a number of patterns in which one object delegates most of its behavior to another, "contained" object. In particular, I'd say "wrapper" applies to the Decorator, Fascade and Adapter patterns, but not to the Proxy pattern because in that case the object you are delegating to is not conceived of as being "contained in" the proxy.
It's not terribly easy to find real-life examples of the Decorator Pattern. One of the examples given by the GoF is also the example given in this MSDN article that describes the Decorator Pattern, which is the Stream family of classes:
Decorator Pattern Example: A CryptoStream
In this example the CryptoStream class inherits from the abstract Stream class and is also given a Stream object reference, overrides the Write method, encrypts the bytes and then calls the Write method on the contained stream to write the encrypted bytes. In fact, if you use Reflector on the .NET Framework CryptoStream class you can see that it has actually been implemented using this pattern. The CryptoStream constructor takes a Stream reference:
public CryptoStream(Stream stream, ICryptoTransform transform, CryptoStreamMode mode)
{
this._stream = stream;
...
}
And the Write method encrypts the buffer before writing it to the stream:
public override void Write(byte[] buffer, int offset, int count)
{
// Bunch of encryption goo...
this._stream.Write(this._OutputBuffer, 0, num3);
}
The Decorator Pattern can provide a very elegant means of composing the behaviors of objects at run time. In the example above, because CryptoStream is passed an abstract Stream object base class reference, it can operate on any type of stream: a FileStream, MemoryStream or any type of stream that you may create in the future. And because CryptoStream itself derives from Stream, it can be passed to other decorators. So if in the future a CompressionStream class were created, you could compose a FileStream that is encrypted and compressed, or a MemoryStream that is compressed by not encrypted. All this can be done dynamically, at runtime, rather than at compile time, as would be the case if you were to attempt this via inheritance.
Pros and Cons of the Decorator Pattern, according to the GoF:
Pros:
- Allows for more flexible composition of behaviors than static inheritance.
- Allows you to break object behaviors into small components (as opposed, for example, to creating a CryptoCompressionStream class that inherits from CryptoStream)
- Can lead to an explosion of small component classes are are difficult to understand.
- Can't rely on object identity remaining unchanged because a client's initial Concrete object may be wrapped in a Decorator at any time.
No comments:
Post a Comment