Introduction
The easiest way to extend some feature of a Class is to use inheritance. But with inheritance, it became very difficult to change the code or even sometimes extend the functionalities. One better solution to overcome some of the deficiencies of inheritance is to use decorator design pattern. This article explains what decorator design pattern is and explains its implementation. The user will learn about the decorator pattern, some of its advantages and how it is implemented in some frameworks such as Java or .NET.
This article will explain the definition of the decorator design pattern and its class diagram and then briefly describe its implementation in Java and .NET.
What is Decorator Design Pattern
We use composition in decorator pattern and what is composition? Consider the following example:
Class TestClass{
Class1 objA = new Class1();
Void myOwnMethod()
{
objA.SomeOneOhterMethod();
}
}
In this example, code TestClass
refers to Class1
This is called composition. Composition means holding the reference to another class. My choice of class names in bad, but for the sake of simplicity, stick with it.
We use inheritance to extend the functionality of any class. In a similar way, we can use composition to extend the functionality of any class. For example, in the above example, TestClass
can extend the functionality of Class1
by simply calling the method of Calss1
and adding its own behavior before or after the method call of Class1
object.
Let’s look at the implementation of how can we use inheritance to write code that is similar to the above code listing in terms of functionality:
Class Class1
{
void SomeOneOhterMethod()
{
}
}
Class TestCalss : Class1
{
void myOwnMethod()
{
Base.SomeOneOtherMethod();
}
}
As you can see, I can use both inheritance and composition to extend the behavior.
Now, let's move to another concept which is called polymorphism or type matching. When using inheritance, one can use the base class in place of its children class. In the second example, you can use the reference of base class Class1
to hold the reference of child class TestClass
. But in the composition example above (first code listing), you can’t use Class1
in place of TestClass
because both classes are of different types.
Here, if I ask you, can you find a way that you can utilize the benefit of type matching and composition at the same time, what would you do? Think about it. You can either use composition or inheritance to achieve the reusability and both come with their advantages and disadvantages. But if you find a way to take the benefits of both methodologies, then what would you do?
Decorator design pattern is an answer to that question.
In decorator pattern, we used composition for extending behavior whereas uses inheritance for matching the type.
Let's see what is the textbook definition of the decorator pattern and then one can compare it to the above example to get the grasp of the concept:
- “Decorator provides a flexible alternative to sub-classing for extending functionality”
AND: - “The decorator pattern attaches additional responsibilities to an object at dynamically.”
This second line of definition explains in the advantages of the decorator pattern.
Class Diagram and Implementing Guidelines
This class diagram shows the implementation of decorator pattern for listing 2. This is a very simple example that shows how can we add benefit for both inheritance and composition in one place.
In this diagram above, TestCalss
is the decorator to Class1
and Class1
is the component. TestClass
extends or inherit from the Class1
as well as it contains a reference (composition) of Class1
. This reference is used to add any additional behavior and inheritance is used for type matching.
Now, let’s look at the textbook class diagram and compare it with the above diagram:
Advantages
Prevent Class Explosion
Suppose you have four classes which extend from the same super-class. Let's suppose a new class is required which adds to some functionality to a superclass. In order to implement, you need to implement this for all the four classes. In this way, you have four new classes for additional functionality for each implementing class. Similarly, all four classes need 16 classes to cater to this change. In total, you have 20 Classes to implement and maintain.
Take, for instance, you have a file type and two classes that extend them Chinese and French file system. Now after some time, you know that some files contain a specific word at the start, others contain frame specific word at the end. You will need to extend from the same class and the class diagram will look like this:
Let’s suppose there is another system for a binary file that is available. Now we have four more classes such as frameSyncChineseFilesBinarySystem
, etc.
This causes four more classes to extend the same base class and will result in a huge number of classes. By using decorator pattern, we can eliminate these problems. For the above example, we can create 2 components, ChineseFileSystem
French File system and 2 decorators around them one is for frame sync and one is for a Binary system. This will reduce the number of classes. We can wrap this BinarySystem
File with all components (e.g., Chinese File System) as well as wrap frame sync with all components. We can also wrap multiple times such as wrapping chinesefileSystem
with FrameSync
and then with Binary File System.
Hence, by not using inheritance and using composition, we prevented the syndrome of too many classes in a design.
New Behavior Can Be Added Dynamically
Since we use composition for adding behavior to the component classes, we can add behavior (even remove) behavior at run-time. Since we have to create the reference to an object at run-time, therefore, we can add or remove the behavior dynamically. On the other side in inheritance, we have to describe the behavior at compile time.
Inheritance for Type Matching So That One Can Mix and Match
Inheritance is used for type matching because in this way, the wrapping (decorator) class can be put in the place of the component class. Because inheritance allows us that base class reference can hold the child object and decorator is a child to the component. Hence, we can retain the benefit of inheritance with the help of decorator design pattern.
Implementations Within .NET and JAVA Framework
Decorator pattern is implemented in Java IO library. If you understand the concept of decorator design patterns, then one can realize different classes’ as wrappers and component. In this way, it becomes extremely easy to implement and can easily utilize the Java IO library.
In .NET, it seems like the streaming library uses decorator pattern, but there is a minor difference.
In the .NET class diagram for IO library, FileStream
and NetworkStream
are component classes whereas BinaryReader
and BinaryWriter
act as the wrapper classes. The wrapper can add new functionality component at run-time since we pass the object of the component during run-time.
The difference is that BinaryReader
wraps FileStream
and NetworkStream
but it does not extend the base stream class. It simply contains the stream. This looks good because no one wants to wrap BinaryWriter
and BinaryReader
together and there is no need for type matching.
Conclusion
In this article, I explain the decorator pattern in a very simplified form as well as briefly describe its implementation in Java and .NET Framework.
Reference
- Book "Design Patterns: Elements of Reusable Object-oriented Software" by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides.