© Vaskaran Sarcar 2020
V. SarcarDesign Patterns in C#https://doi.org/10.1007/978-1-4842-6062-3_7

7. Decorator Pattern

Vaskaran Sarcar1 
(1)
Garia, Kolkata, West Bengal, India
 

This chapter covers the Decorator pattern.

GoF Definition

Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

Concept

From the GoF definition, it is evident that this pattern uses an alternative to subclassing (i.e., inheritance). If inheritance is not allowed, how do you proceed? Yes, you guessed it right. It prescribes you to use composition instead of inheritance.

By following the SOLID principle, this pattern promotes the concept where your class is closed for modification but open for extension. (If you want to learn more about SOLID principles, go to https://en.wikipedia.org/wiki/SOLID_(object-oriented_design).) Using this pattern, you can add special functionality to a specific object without altering the underlying class.

A decorator is just like a wrapper (or topping) that surrounds the original object and adds additional functionality to it. This is why the Decorator pattern is also called a Wrapper pattern. This pattern is most effective when you add decorators dynamically. Since decorators are often added dynamically, it’s perfectly fine if you do not want them in a later phase of development, because the original object may still work.

Real-World Example

Suppose that you own a single-story house, and you decide to build an additional floor on top of it. You may not want to change the architecture of the ground floor, but you may want to employ a new design for the newly added floor that can fit on top of the existing architecture.

Figures 7-1, 7-2, and 7-3 illustrate this concept.
../images/463942_2_En_7_Chapter/463942_2_En_7_Fig1_HTML.jpg
Figure 7-1

Original house

../images/463942_2_En_7_Chapter/463942_2_En_7_Fig2_HTML.jpg
Figure 7-2

Original house with a decorator (the additional floor is built on top of original structure)

../images/463942_2_En_7_Chapter/463942_2_En_7_Fig3_HTML.jpg
Figure 7-3

Applying an additional decorator on top of the existing decorator and modifying the house (now painting the house)

Note

The case shown in Figure 7-3 is optional. You can use an existing decorator object to enhance the behavior, or you can create a new decorator object and add the new behavior to it. In step 2, you could also directly paint the original house. You don’t need to start painting once the new floor is added.

Computer-World Example

Suppose that you want to add border properties to a GUI-based toolkit. You could do this using inheritance, but that cannot be treated as an ultimate solution because you may not have absolute control over everything since the beginning. So, this technique is static by nature.

In this context, decorators can offer you a flexible approach. They promote the concept of dynamic choices. For example, you can wrap the component in another object (similar to Figures 7-2 and 7-3). The enclosing object is called a decorator, and it must conform to the interface of the component that it decorates. It forwards the requests to the original component and can perform additional operations before or after those requests. In fact, this concept allows you to add an unlimited number of responsibilities.

Implementation

In this example, five players are involved: AbstractHome, ConcreteHome, AbstractDecorator, FloorDecorator, and PaintDecorator.

AbstractHome is defined as follows.
    abstract class AbstractHome
    {
        public double AdditionalPrice { get; set; }
        public abstract void MakeHome();
    }
A concrete implementor of AbstractHome must implement the MakeHome() method . In addition to this, you can set a price by using the AdditionalPrice property. This is why a concrete class called ConcreteHome inherits from AbstractHome, completes the original structure, and looks like the following (I assume that once the home is built, there is no immediate modification needed; so, AdditionalPrice is initially set to 0).
    class ConcreteHome : AbstractHome
    {
        public ConcreteHome()
        {
            AdditionalPrice = 0;
        }
        public override void MakeHome()
        {
            Console.WriteLine($"Original House is constructed.Price for this 10000$");
        }
    }
At this moment, you can opt for an additional floor to this existing home, or you may want to paint the home or you may want to do both. So, FloorDecorator and PaintDecorator both come into the picture. Though it was not strictly needed, to share the common code, both decorators inherit from AbstractDecorator , which has the following structure.
abstract class AbstractDecorator : AbstractHome
    {
        protected AbstractHome home;
        public AbstractDecorator(AbstractHome home)
        {
            this.home = home;
            this.AdditionalPrice = 0;
        }
        public override void MakeHome()
        {
            home.MakeHome();
        }
    }

Notice that AbstractDecorator holds a reference to AbstractHome. So, the concrete decorators (FloorDecorator or PaintDecorator in this example) are decorating an instance of AbstractHome.

Now let’s look at the structure of a concrete decorator, FloorDecorator, which is as follows.
    // Floor Decorator used to add a floor
    class FloorDecorator : AbstractDecorator
    {
        public FloorDecorator(AbstractHome home) : base(home)
        {
            this.AdditionalPrice = 2500;
        }
        public override void MakeHome()
        {
            base.MakeHome();
            // Adding a floor on top of original house.
            AddFloor();
        }
        private void AddFloor()
        {
            Console.WriteLine($"-Additional Floor added.Pay additional {AdditionalPrice}$ for it .");
        }
    }

You can see that FloorDecorator can add a floor (using the AddFloor() method), and when you use it, you must pay an additional $2500 for the additional construction. More importantly, before adding a floor, it calls the MakeHome() method of the AbstractHome class, which in turn calls the MakeHome() method from a concrete implementation of AbstractHome (i.e., ConcreteHome).

PaintDecorator acts similarly, but you have to pay more for it. (Yes, I assume that you are using luxurious paints for your home.)

Class Diagram

Figure 7-4 shows the most important parts of the class diagram.
../images/463942_2_En_7_Chapter/463942_2_En_7_Fig4_HTML.jpg
Figure 7-4

Class diagram. Client class is not shown here.

Solution Explorer View

Figure 7-5 shows the high-level structure of the program.
../images/463942_2_En_7_Chapter/463942_2_En_7_Fig5_HTML.jpg
Figure 7-5

Solution Explorer view

Demonstration

Here’s the complete implementation, which tested two scenarios (marked with #region). In scenario 1, I add one floor to the existing home and then paint it. In scenario 2, I paint the original home and then add two floors on top of the existing architecture.
using System;
namespace DecoratorPatternDemo
{
    abstract class AbstractHome
    {
        public double AdditionalPrice { get; set; }
        public abstract void MakeHome();
    }
    class ConcreteHome : AbstractHome
    {
        public ConcreteHome()
        {
            AdditionalPrice = 0;
        }
        public override void MakeHome()
        {
            Console.WriteLine($"Original House is constructed.Price for this $10000");
        }
    }
    abstract class AbstractDecorator : AbstractHome
    {
        protected AbstractHome home;
        public AbstractDecorator(AbstractHome home)
        {
            this.home = home;
            this.AdditionalPrice = 0;
        }
        public override void MakeHome()
        {
            home.MakeHome();//Delegating task
        }
    }
    // Floor Decorator is used to add a floor
    class FloorDecorator : AbstractDecorator
    {
        public FloorDecorator(AbstractHome home) : base(home)
        {
            //this.home = home;
            this.AdditionalPrice = 2500;
        }
        public override void MakeHome()
        {
            base.MakeHome();
            // Adding a floor on top of original house.
            AddFloor();
        }
        private void AddFloor()
        {
            Console.WriteLine($"-Additional Floor added.Pay additional ${AdditionalPrice} for it .");
        }
    }
    // Paint Decorator used to paint the home.
    class PaintDecorator : AbstractDecorator
    {
        public PaintDecorator(AbstractHome home):base(home)
        {
            //this.home = home;
            this.AdditionalPrice = 5000;
        }
        public override void MakeHome()
        {
            base.MakeHome();
            // Painting home.
            PaintHome();
        }
        private void PaintHome()
        {
            Console.WriteLine($"--Painting done.Pay additional ${AdditionalPrice} for it .");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Decorator pattern Demo*** ");
            #region Scenario-1
            Console.WriteLine(" **Scenario-1:");
            Console.WriteLine("**Building home.Adding floor and then painting it.**");
            AbstractHome home = new ConcreteHome();
            Console.WriteLine("Current bill breakups are as follows:");
            home.MakeHome();
            // Applying a decorator
            // Adding a floor
            home = new FloorDecorator(home);
            Console.WriteLine(" Floor added.Current bill breakups are as follows:");
            home.MakeHome();
            // Working on top of the previous decorator.
            // Painting the home
            home = new PaintDecorator(home);
            Console.WriteLine(" Paint applied.Current bill breakups are as follows:");
            home.MakeHome();
            #endregion
            #region Scenario-2
            Console.WriteLine(" **Scenario-2:");
            Console.WriteLine("**Building home,painting it and then adding two additional floors on top of it.**");
            // Fresh start once again.
            home = new ConcreteHome();
            Console.WriteLine(" Going back to original home.Current bill breakups are as follows:");
            home.MakeHome();
            // Applying paint on original home.
            home = new PaintDecorator(home);
            Console.WriteLine(" Paint applied.Current bill breakups are as follows:");
            home.MakeHome();
            // Adding a floor on the painted home.
            home = new FloorDecorator(home);
            Console.WriteLine(" Floor added.Current bill breakups are as follows:");
            home.MakeHome();
            // Adding another floor on the current home.
            home = new FloorDecorator(home);
            Console.WriteLine(" Floor added.Current bill breakups are as follows:");
            home.MakeHome();
            #endregion
            Console.ReadKey();
        }
    }
}

Output

***Decorator pattern Demo***
**Scenario-1:
**Building home. Adding floor and then painting it.**
Current bill breakups are as follows:
Original House is constructed. Price for this $10000
Floor added. Current bill breakups are as follows:
Original House is constructed.Price for this $10000
-Additional Floor added.Pay additional $2500 for it.
Paint applied. Current bill breakups are as follows:
Original House is constructed.Price for this $10000
-Additional Floor added. Pay additional $2500 for it.
--Painting done. Pay additional $5000 for it.
**Scenario-2:
**Building home, painting it and then adding two additional floors on top of it.**
Going back to original home. Current bill breakups are as follows:
Original House is constructed. Price for this $10000
Paint applied. Current bill breakups are as follows:
Original House is constructed. Price for this $10000
--Painting done. Pay additional $5000 for it.
Floor added.Current bill breakups are as follows:
Original House is constructed.Price for this $10000
--Painting done.Pay additional $5000 for it.
-Additional Floor added. Pay additional $2500 for it.
Floor added.Current bill breakups are as follows:
Original House is constructed. Price for this $10000
--Painting done.Pay additional $5000 for it.
-Additional Floor added.Pay additional $2500 for it.
-Additional Floor added.Pay additional $2500 for it.

Q&A Session

7.1 Can you explain how composition promotes a dynamic behavior that inheritance cannot?

When a derived class inherits from a base class, it inherits the behavior of the base class at that time only. Though different subclasses can extend the base or parent class in different ways, this type of binding is known at compile time. So, the method is static. But by using the concept of composition, as in the previous example, you get dynamic behavior.

When you design a parent class, you may not have enough visibility about what kind of additional responsibilities your clients may want in some later phase. Since the constraint is that you cannot modify the existing code, in this case, object composition not only outclasses inheritance, but it also ensures that you are not introducing bugs in the old architecture.

Lastly, in this context, you must try to remember a key design principle that says classes should be open for extension but closed for modification.

7.2 What are the key advantages of using a decorator?

Here are some of the key advantages.
  • The existing structure is untouched, so you cannot introduce bugs there.

  • New functionalities can be easily added to an existing object.

  • You can not only add a behavior to an interface, but you can alter the behavior too.

  • You do not need to predict/implement all the supported functionalities at once (for example, in the initial design phase). You can develop incrementally. For example, you can add decorator objects one by one to support your needs. You must acknowledge that if you make a complex class first and then want to extend the functionalities, it will be a tedious process.

7.3 How is the overall design pattern different from inheritance ?

You can add, alter, or remove responsibilities by simply attaching or detaching decorators. But with simple inheritance techniques, you need to create new classes for new responsibilities. So, you may end up with a complex system.

Consider the example again. Suppose that you want to add a new floor, paint the house, and do some extra work. To fulfill this need, you can start with FloorDecorator because it is already providing the support to add a floor, and then use PaintDecorator to paint the house. Then you need to add a simple wrapper to complete those additional responsibilities.

But if you start with inheritance, and then you may have multiple subclasses; for example, one for adding a floor and one for painting the house, as shown in Figure 7-6 (a hierarchical inheritance).
../images/463942_2_En_7_Chapter/463942_2_En_7_Fig6_HTML.jpg
Figure 7-6

A hierarchical inheritance

So, if you need an additional painted floor with some extra features, you may need to end up with a design like in Figure 7-7.
../images/463942_2_En_7_Chapter/463942_2_En_7_Fig7_HTML.jpg
Figure 7-7

A class (Extra Features) needs to inherit from multiple base classes

Now you feel the heat of the “diamond effect” because in many programming languages, including C#, multiple base classes are not allowed.

You also discover that the inheritance mechanism is not only much more challenging and time-consuming compared to the Decorator pattern, but it may promote duplicate code in your application. Lastly, do not forget that inheritance promotes only compile-time binding (not dynamic binding).

7.4 Why are you creating a class with a single responsibility? You could make a subclass that can simply add a floor and then paint. In that case, you may end up with fewer subclasses. Is this correct?

If you are familiar with the SOLID principles, you know that there is a principle called single responsibility . The idea behind this principle is that each class should have responsibility for a single part of the functionality provided in the software. The Decorator pattern is effective when you use the single responsibility principle because you can simply add or remove responsibilities dynamically.

7.5 What are the disadvantages associated with this pattern?

I believe that if you are careful, there are no significant disadvantages. But if you create too many decorators in the system, it will be hard to maintain and debug. So, in that case, they can create unnecessary confusion.

7.6 In the example, the AbstractDecorator class is abstract, but there is no abstract method in it. How is this possible?

In C#, a class can be abstract without containing an abstract method, but the reverse is not true. In other words, if a class contains at least one abstract method, it means that the class is incomplete, and you are forced to mark it with the abstract keyword.

Also, if you read the comment in Figure 7-8, you are delegating the task to a concrete decorator, in this case, because you want to use and instantiate the concrete decorators only.
../images/463942_2_En_7_Chapter/463942_2_En_7_Fig8_HTML.jpg
Figure 7-8

An abstract class: AbstractDecorator

So, in this example, you cannot simply instantiate an AbstractDecorator instance, because it is marked with the abstract keyword.

The following line creates a compilation error.
AbstractDecorator abstractDecorator = new AbstractDecorator();
saying “CS0144    Cannot create an instance of the abstract class or interface 'AbstractDecorator'”

7.7 Are decorators used for dynamic binding only?

No. You can use the concept for both static and dynamic binding. But dynamic binding is its strength, so I concentrated on that here. The GoF definition also focuses on dynamic binding only.

Note

The I/O streams implementations in the .NET Framework, .NET Core, and Java use the Decorator pattern. For example, the BufferedStream class inherits from the Stream class. Note the presence of two overloaded constructors in this class; each of them takes a Stream (Parent class) as a parameter (just like demonstration 1). When you see this kind of construct, there is a possibility that you are seeing an example of the Decorator pattern. BufferedStream is acting like a decorator in .NET.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.84.32