Decorator pattern is a design pattern belonging to the structural group. It provides the ability to extend an object by “attaching” a new variable or method to an object. Object is oblivious to the fact that it is being “decorated” with new methods and variables. This design pattern is suitable for systems that need to expand continuously. Decorator idea is relatively close to inheritance and you can easily understand this design pattern if you understand the idea of inheritance.
In this lesson we will look at the idea, design, and implementation of this pattern in detail.
What is the Decorator Pattern?
The Decorator design pattern helps to add new states (i.e. member variables) and operations (methods) to an existing object.
Do you sound familiar? Add new variables and methods to an existing object. So that’s not inheritance? In fact, you can think of Decorator as a type of “inheritance”, because their ideas are quite close. However, Decorator “inheritance” is done at the object level, rather than the class level. Because of its object-level implementation, Decorator “inheritance” occurs at runtime, rather than at compile time type definition.
Let’s take another example of an image displayed on the screen. Programs that support image viewing/processing can incorporate many decorative elements such as borders, tags. These decorative elements appear above the image but do not affect the original image file. The combination of the original image and the decorations creates a new object. You see this composite image.
The Decorator pattern is exactly the same idea. Each decorative element for the original image is an independent object. And there are also countless ways to decorate the photo. From one original photo you can create a lot of pictures with different decorations. You can even combine decorations to create unique photos.
In summary, the Decorator pattern has the following characteristics:
- Can add (dynamically) new members (decorators) to the object;
- The original Object does not change and knows nothing about what is added to it;
- So you don’t need to build a giant class with everything inside;
- Decorative objects are independent of each other and can be combined.
Thus, in general, Decorator is close to inheritance. So why not always use inheritance. If a class is developed by someone else and marked sealed, you cannot inherit from that class anymore. At this point, if you want to expand it, think of Decorator. If you need to extend the object at runtime, you need Decorator.
In addition, Decorator has a variety of combinations to extend the object that if you use inheritance, the result will be a lot of subclasses that make it difficult to maintain the code later.
Design Decorator Pattern
Below is a UML design diagram of the Decorator design pattern. If you don’t remember the symbols, reread the UML section of the Design Pattern overview lesson.
The components of this design are as follows:
Component: the original class. You can think of it as the original image, or the parent class in inheritance.
Decorator: decoration class. You can think of it as a subclass (in inheritance), or a decorated image.
Both Component and Decorator must implement a common interface IComponent.
addedState and addedBehavior are Decorator’s own variables and methods that add to the original object (of the Component). It’s easy to imagine it as private member variables and methods of subclasses.
Operation: decorator not only complements but can also replace the method of the original class. If there are methods to replace, it must be specified in the common interface between the Component and the Decorator. It’s easy to imagine it’s like overriding methods in inheritance.
Between Decorator and IComponent there is also an aggregate relationship (has-a), expressed through a private component variable of type IComponent inside the Decorator. It is very close to the idea of creating an inheritance tree of object-oriented programming. Because of this relationship, an extended object can be extended by another decorator creating an entire “inheritance tree” in the style of Decorator. This is like a redecorated photo that can continue to be decorated further.
Implementing Decorator Pattern
Once you understand the idea of the Decorator pattern, it’s time to do a basic illustration.
using System;
using static System.Console;
namespace P01_Concept
{
interface IComponent
{
string Operation();
}
class Component : IComponent
{
public string Operation()
{
return “Hello world! This is the original object”;
}
}
class DecoratorA : IComponent
{
private readonly IComponent _component;
public DecoratorA(IComponent component)
{
_component = component;
}
// consider “inherit” this method from the original object
// if you want you can “simulate overriding” by changing this method content
public string Operation()
{
return _component.Operation();
}
//add this method to the original object
public string AddedBehavior()
{
return “This is the A Decorator object”;
}
}
class DecoratorB : IComponent
{
private readonly IComponent _component;
public DecoratorB(IComponent component)
{
_component = component;
}
// simulate overriding Operation
public string Operation()
{
var s = _component.Operation();
return $”{s}.But I was ‘overrode'”;
}
}
class Program
{
static void Main(string[] args)
{
Title = “DECORATOR DESIGN PATTERN”;
IComponent orgComponent = new Component();
DecoratorA aComponent = new DecoratorA(orgComponent);
DecoratorB bComponent = new DecoratorB(orgComponent);
DecoratorA abComponent = new DecoratorA(bComponent);
ForegroundColor = ConsoleColor.Green;
WriteLine($”Original object: {orgComponent.Operation()}”);
ForegroundColor = ConsoleColor.Yellow;
WriteLine($”A Decorator object: {aComponent.Operation()}. {aComponent.AddedBehavior()}”);
ForegroundColor = ConsoleColor.Cyan;
WriteLine($”B Decorator object: {bComponent.Operation()}”);
ForegroundColor = ConsoleColor.Magenta;
WriteLine($”AB Decorator object: {abComponent.Operation()}”);
ReadKey();
}
}
}
In this example reuse the correct names in the UML design diagram. It also illustrates the correlation between Decorator and inheritance.
Application of the Decorator Pattern
The Decorator design pattern has a number of practical applications.
The first is in graphics, video, audio. For example, so that streaming video can be compressed in different ratios; Audio can offer multiple conversion services at the same time.
Second, use it in some I/O APIs. For example, the .NET Stream class string (Stream => FileStream, MemoryStream, NetworkStream => BinaryWriter/Reader, StreamWriter/Reader) uses Decorator. If you’ve ever worked with one of the above stream types, you’ll probably notice: (1) adapters extend the backing store stream but don’t inherit from it; (2) in an object, for example a NetworkStream or a StreamWriter, there is an object of Stream; (3) stream types are mutually extensible. Recall the design model of Decorator to see if there are any similarities.
Third, browsers and mobile apps use this template to create the right interface for each type of screen size.
- In short, in the following situations you should think of Decorator:
- Want to extend sealed class;
- Extend or change objects “dynamically” at runtime without touching the original object;
- Do not want to create subclasses (avoid using inheritance).
Conclusion
In this lesson you became familiar with the Decorator design pattern. This is a pretty simple design pattern to implement. Decorator has many practical applications and you will probably need Decorator as an alternative to inheritance. Don’t forget to follow our blog for other useful programming lessons.