Click here to Skip to main content
15,885,278 members
Articles / Programming Languages / C#

Decorator Pattern in C# - 3 Versions

Rate me:
Please Sign up or sign in to vote.
4.55/5 (9 votes)
1 Apr 2022MIT12 min read 9.6K   99   16   2
Tutorial on Decorator Pattern in C#, showing 3 versions in C#
This article is a tutorial on Decorator Pattern in C#. First, we show the Classic Decorator pattern, which is described in the literature and by GoF books. Then we show 2 alternative versions, that can be viewed as “modern-C#-enabled” versions of the pattern. The intended audience is Intermediate C# programmer and above.

Introduction

Decorator Pattern is one fascinating and very popular pattern. The pattern has the purpose to add additional functionality to an existing object dynamically. Decorators provide an alternative to subclassing for extending functionality. While it is possible to add functionality to a whole class of objects by subclassing of object’s class, the Decorator pattern aims to add functionality to just a single object and leave other objects of that class unchanged.

It is sometimes commented that, by enhancing the existing type without modifying the original type, the pattern follows Open-Close Principle.

What is very interesting, the pattern achieves its goal by doing things the wrong way, against OO recommendations.

Composition vs. Inheritance: How to Choose?

A typical question in OOA/OOD courses is, for a given class/object A, when creating a new class/object B that is going to reuse class/object A, how and based on what criteria to choose between Composition and Inheritance? Recommendations are usually given: If you plan to reuse the Public interface of an existing class A, use Inheritance. If you plan to reuse just the functionality of an existing class A, go for Composition.

So, Decorator Pattern is doing exactly the opposite. It plans to reuse the Public Interface of an existing class but goes for Composition. That decision has some consequences regarding problems with extending of Public Interface, as we will see later.

Classic Decorator Pattern – Version 1

Classic Decorator is a version of the pattern that was offered in the GoF book and is often mentioned in literature. Typically, you would have some interface IComponent abstracting some real class ComponentA. Our wish is to replace the object of class ComponentA with some object that will provide more/enhanced functionality, let’s call it DecoratorB, that implements the same interface IComponent. What we want it to replace:

C#
IComponent comp= new ComponentA();

with:

C#
IComponent comp= new DecoratorB(/*--some parameters--*/);

Where DecoratorB “improves” object ComponentA in a certain way.

In this pattern, DecoratorB goes for composition, it adds ComponentA as its component, and reimplements interface IComponent from scratch, sometimes just passing methods calls to ComponentA methods.

So, let us assume we have this IComponent interface and ComponentA class implementations:

C#
public interface IComponent                   //(1)
{
    string StateI { get; set; }               //(2)
    string MethodI1(bool print = true);       //(3)
}

public class ComponentA : IComponent          //(4)
{
    public string StateI { get; set; }        //(5)
    public string StateA { get; set; }        //(6)

    public ComponentA()                       //(7)
    { }

    public string MethodI1(bool print = true) //(8)
    {
        string text = "ComponentA.MethodI1";
        if (print) Console.WriteLine(text);
        return text;
    }

    public string MethodA1()                  //(9)
    {
        string text = "ComponentA.MethodA1";
        Console.WriteLine(text);
        return text;
    }
}

If you look at the IComponent interface, you see it exposes publicly one property (2) and one method (3).
Class ComponentA is our core component/functionality class. You can see, that of course, implements the public interface of IComponent (5), (8), but in addition, has some additions to class public interface at (6) and (9). When accessing class through interface IComponent, we are, of course, limited to properties/methods exposed by that interface, meaning that property (6) and method (9) will not be accessible.

Typical usage of the above classes would look like this:

C#
Console.WriteLine("IComponent================================");
IComponent I = new ComponentA();      //(20)
I.StateI = "123";
//I.StateA = "123";                   //not possible
string tmpI1 = I.MethodI1();          //(21)
//string tmpI2 = I.MethodA1();        //not possible  //(22)

Console.WriteLine("ComponentA================================");
ComponentA A = new ComponentA();      //(23)
A.StateI = "123";
A.StateA = "123";
string tmpA1 = A.MethodI1();
string tmpA2 = A.MethodA1();

Now let us see what a sample decorator class would look like:

C#
public class DecoratorB : IComponent             //(10)
{
    public string StateI { get; set; }           //(11)
    public string StateB { get; set; }           //(12)

    private IComponent component = null;         //(13)

    public DecoratorB(IComponent comp)           //(14)
    {
        component = comp;
    }

    public string MethodI1(bool print = true)    //(15)
    {
        string baseTmp = component.MethodI1(false);
        string text = baseTmp + " " + "DecoratorB.MethodI1";
        if (print) Console.WriteLine(text);
        return (text);
    }

    public string MethodB1()                     //(16)
    {
        string text = "DecoratorB.MethodB1";
        Console.WriteLine(text);
        return text;
    }
}

The key thing to notice in the Decorator class is how the constructor (14) passed IComponent is assigned to a private attribute (13). That is the “composition” we were talking about and from there all magic of decorator is coming.

Decorator (10) now has the ability to completely reuse functionality of contained object in (13) and expose its own interface, in which it is free to reuse or modify the behavior of object (13).

In (15) method MethodI1 we see how DecoratorB provides its own implementation of the interface (10) and in that process, it reuses MethodI1 of the contained object (13).

In addition to the public interface it inherited in (10), DecoratorB can add its own properties (12) and methods (16) that add some new functionality to the class.

Typical usage of DecoratorB would look like:

C#
DecoratorB B = new DecoratorB(new ComponentA()); //(24)
B.StateI = "123";
//B.StateA = "123";                              //not possible
B.StateB = "123";
string tmpB1 = B.MethodI1();                     //(25)
//string tmpB2 = B.MethodA1();                   //not possible //(26)
string tmpB3 = B.MethodB1();

Look at call (25). That is the real reason for the usage of and strength of the Decorator pattern. That is a call to MethodI1 of class DecoratorB, which is using and enhancing the call to MethodI1 of class ComponentA. Since MethodI1 is part of interface IComponent, even if the resulting object (24) is accessed through reference to IComponent, the call will be available and it will work. Consumer of reference to IComponent even needs not to be aware if he is using original ComponentA or decorated version. The whole point of why we are doing this Decorator pattern lies in the power of this call. Of course, this is tutorial-level-code, in real life there will be more methods MethodI1, MethodI2, MethodI3, etc. that will all have that power.

We can see that in (26) it is not possible to access the method from ComponentA that is not exposed through interface IComponent. That is a problem because we might have several Components, let’s say ComponentA, ComponentA2, ComponentA3, etc. and each might have its own specific setters to configure itself. The main reason why this is happening is that, again, we do not inherit from ComponentA, we contain an object of class ComponentA and we access it only through interface IComponent.

This is one serious deficiency of the Decorator pattern since ComponentA might need some configuring/setup before is ready for reuse by DecoratorB. It is often bypassed in two ways:

  1. ComponentA is configured before it is passed to DecoratorB in (24)
  2. ComponentA constructor would have parameters to configure itself

Let us create one more Decorator class:

C#
public class DecoratorC : IComponent    //(40)
{
    public string StateI { get; set; }
    public string StateC { get; set; }  //(41)

    private IComponent component = null;

    public DecoratorC(IComponent comp)
    {
        component = comp;
    }

    public string MethodI1(bool print = true)
    {
        string baseTmp = component.MethodI1(false);
        string text = baseTmp + " " + "DecoratorC.MethodI1";
        if (print) Console.WriteLine(text);
        return text;
    }

    public string MethodC1()            //(42)
    {
        string text = "DecoratorC.MethodC1";
        Console.WriteLine(text);
        return text;
    }
}

As you can see, DecoratorC is almost the same as DecoratorB. That was done deliberately for the purpose of this article.

Notice that in (41) and (42) DecoratorC is defining some public properties/methods that are specific to itself.

Here is what now usage of DecoratorC looks like:

C#
DecoratorC C = new DecoratorC(new DecoratorB(new ComponentA()));  //(27)
C.StateI = "123";
//C.StateA = "123";               //not possible
//C.StateB = "123";               //not possible
C.StateC = "123";
string tmpC1 = C.MethodI1();      //(28)
//string tmpC2 = C.MethodA1();    //not possible  //(29)
//string tmpC3 = C.MethodB1();    //not possible  //(30)
string tmpC4 = C.MethodC1();

You can see in (27) how multiple decorators can be applied to a component. Let us say immediately, that nothing from a code design point prevents us from applying decorators in a different order, instead of first DecoratorB then DecoratorC, we can apply them in a different order. But, the resulting functionality might differ, so the order is relevant.

Look at call (28). Again, that is the real reason for the usage of and strength of the Decorator pattern. That is a call to MethodI1 of class DecoratorC, which is using and enhancing call to MethodI1 of class DecoratorB, and that one is using and enhancing call to MethodI1 of class ComponentA. Again, if we assign (27) to reference to IComponent, the consumer of reference to IComponent even needs not to be aware if he is using original ComponentA or decorated version. The whole point of why we are doing this Decorator pattern lies in the power of this call. Again, in real life, there will be more methods MethodI1, MethodI2, MethodI3, etc. that will all have that power.

You can see in (29) and (30) that access to some public methods of ComponentA and DecoratorB is not possible. As mentioned above, that is a serious deficiency of the Decorator pattern.

Here is the class diagram and code of the whole project Classic Decorator.

Image 1

C#
public interface IComponent  //(1)
{
    string StateI { get; set; }   //(2)
    string MethodI1(bool print = true);   //(3)
}

public class ComponentA : IComponent   //(4)
{
    public string StateI { get; set; }   //(5)
    public string StateA { get; set; }   //(6)

    public ComponentA()  //(7)
    { }

    public string MethodI1(bool print = true)   //(8)
    {
        string text = "ComponentA.MethodI1";
        if (print) Console.WriteLine(text);
        return text;
    }

    public string MethodA1()   //(9)
    {
        string text = "ComponentA.MethodA1";
        Console.WriteLine(text);
        return text;
    }
}

public class DecoratorB : IComponent   //(10)
{
    public string StateI { get; set; }   //(11)
    public string StateB { get; set; }   //(12)

    private IComponent component = null;    //(13)

    public DecoratorB(IComponent comp)   //(14)
    {
        component = comp;
    }

    public string MethodI1(bool print = true)  //(15)
    {
        string baseTmp = component.MethodI1(false);
        string text = baseTmp + " " + "DecoratorB.MethodI1";
        if (print) Console.WriteLine(text);
        return (text);
    }

    public string MethodB1()  //(16)
    {
        string text = "DecoratorB.MethodB1";
        Console.WriteLine(text);
        return text;
    }
}

public class DecoratorC : IComponent  //(40)
{
    public string StateI { get; set; }
    public string StateC { get; set; }  //(41)

    private IComponent component = null;

    public DecoratorC(IComponent comp)
    {
        component = comp;
    }

    public string MethodI1(bool print = true)
    {
        string baseTmp = component.MethodI1(false);
        string text = baseTmp + " " + "DecoratorC.MethodI1";
        if (print) Console.WriteLine(text);
        return text;
    }

    public string MethodC1()  //(42)
    {
        string text = "DecoratorC.MethodC1";
        Console.WriteLine(text);
        return text;
    }
}

class Client
{
    static void Main(string[] args)
    {
        Console.WriteLine("IComponent================================");
        IComponent I = new ComponentA();    //(20)
        I.StateI = "123";
        //I.StateA = "123";                 //not possible
        string tmpI1 = I.MethodI1();        //(21)
        //string tmpI2 = I.MethodA1();      //not possible  //(22)

        Console.WriteLine("ComponentA================================");
        ComponentA A = new ComponentA();    //(23)
        A.StateI = "123";
        A.StateA = "123";
        string tmpA1 = A.MethodI1();
        string tmpA2 = A.MethodA1();

        Console.WriteLine("DecoratorB================================");
        DecoratorB B = new DecoratorB(new ComponentA()); //(24)
        B.StateI = "123";
        //B.StateA = "123";                 //not possible
        B.StateB = "123";
        string tmpB1 = B.MethodI1();        //(25)
        //string tmpB2 = B.MethodA1();      //not possible //(26)
        string tmpB3 = B.MethodB1();

        Console.WriteLine("DecoratorC================================");
        DecoratorC C = new DecoratorC(new DecoratorB(new ComponentA()));  //(27)
        C.StateI = "123";
        //C.StateA = "123";                //not possible
        //C.StateB = "123";                //not possible
        C.StateC = "123";
        string tmpC1 = C.MethodI1();       //(28)
        //string tmpC2 = C.MethodA1();     //not possible  //(29)
        //string tmpC3 = C.MethodB1();     //not possible  //(30)
        string tmpC4 = C.MethodC1();

        Console.WriteLine("Collection================================");
        List<IComponent> list = new List<IComponent>();
        list.Add(new ComponentA());
        list.Add(new DecoratorB(new ComponentA()));
        list.Add(new DecoratorC(new DecoratorB(new ComponentA())));

        foreach (IComponent iComp in list)
        {
            iComp.StateI = "123";
            string tmpII1 = iComp.MethodI1();
        }

        Console.ReadLine();
    }
}

Here is the result of sample execution:

Image 2

Dynamically Selecting a Type of Component

What Decorator pattern makes possible, is to dynamically at runtime select which type of component application will use. Let us look at this code:

C#
ComponentA compA = new ComponentA();
compA.StateA = "123"; // some configuring

IComponent comp = null;
int selection = GetSelectionFromGui();

switch (selection)
{
    case 1:
        comp = new DecoratorB(compA);
        break;
    case 2:
        comp = new DecoratorC(compA);
        break;
    default:
        comp = compA;
        break;
}

string result = comp.MethodI1();

You can see, that user input will at runtime decide component type.

Decorator Cycles

Nothing in the code is preventing us from applying the same decorator more than once. For example, look at the code:

C#
IComponent cc = new DecoratorB(new DecoratorC(new DecoratorB(new ComponentA())));

In literature [1], you can find articles discussing how to detect Decorator cycles, and possibly forbid them. We will not be discussing that here.

Problem with Fat Classic Decorator

We said that the Decorator pattern is about composability, that is reused class is not inherited but composed into decorator class. One side-effect of that is that the decorator does not inherit the public interface of the reused class, but needs to explicitly implement each and every method that needs to be reused/exposed publicly. Creating and maintaining all those methods requires some effort if the number is bigger.

Let us see how the class diagram looks in the case of a fat classic observer, that has five public methods to expose.

Image 3

It can be seen that the number of methods in solution grows significantly. Most of the methods might just be passing calls to contained object, as can be seen in this example implementation of DecoratorB.

C#
public class DecoratorB : IComponent
{
    public string StateI { get; set; }
    public string StateB { get; set; }

    private IComponent component = null;

    public DecoratorB(IComponent comp)
    {
        component = comp;
    }

    public string MethodI1(bool print = true)
    {
        return component.MethodI1();
    }

    public string MethodI2(bool print = true)
    {
        return component.MethodI2();
    }

    public string MethodI3(bool print = true)
    {
        return component.MethodI3();
    }

    public string MethodI4(bool print = true)
    {
        return component.MethodI4();
    }

    public string MethodI5(bool print = true)
    {
        return component.MethodI5();
    }

    public string MethodB1()
    {
        string text = "DecoratorB.MethodB1";
        Console.WriteLine(text);
        return text;
    }
}

More realistically, in practice, there will be 20+ methods per class that need to be reused/publicly exposed. There are some developer tools like ReSharper that can assist the developer in creating and maintaining automatically all those methods, but the responsibility still lies on the developer/implementer.

Generics Decorator Pattern – Version 2

Generics Decorator pattern version can be found in recent literature. The idea of the Decorator being based on composability stays the same, just the method of passing type information is different. From the point of the purpose of the pattern and main design ideas, many things are similar. I got the idea for this version of the Decorator pattern and core code from [2].

The main trick is to pass class/type information via generics parameter types. Creation of contained component object is passed to default constructor of type parameter class. So, analogous code sample to our previous code will now look like this:

C#
IComponent C = new DecoratorC<DecoratorB<ComponentA>>();

Since many things regarding usage are similar to the previous version, we will go immediately to the class diagram and code:

Image 4

C#
public interface IComponent
{
    string StateI { get; set; }
    string MethodI1(bool print = true);
}

public class ComponentA : IComponent
{
    public string StateI { get; set; }
    public string StateA { get; set; }

    public ComponentA()
    { }

    public string MethodI1(bool print = true)
    {
        string text = "ComponentA.MethodI1";
        if (print) Console.WriteLine(text);
        return text;
    }

    public string MethodA1()
    {
        string text = "ComponentA.MethodA1";
        Console.WriteLine(text);
        return text;
    }
}

public class DecoratorB<T> : IComponent  //(50)
        where T : IComponent, new()
{
    public string StateI { get; set; }
    public string StateB { get; set; }

    private T component = new T();   //(51)

    public DecoratorB()
    {
    }

    public string MethodI1(bool print = true)
    {
        string baseTmp = component.MethodI1(false);
        string text = baseTmp + " " + "DecoratorB.MethodI1";
        if (print) Console.WriteLine(text);
        return (text);
    }

    public string MethodB1()
    {
        string text = "DecoratorB.MethodB1";
        Console.WriteLine(text);
        return text;
    }
}

public class DecoratorC<T> : IComponent
        where T : IComponent, new()
{
    public string StateI { get; set; }
    public string StateC { get; set; }

    private T component = new T();

    public DecoratorC()
    {
    }

    public string MethodI1(bool print = true)
    {
        string baseTmp = component.MethodI1(false);
        string text = baseTmp + " " + "DecoratorC.MethodI1";
        if (print) Console.WriteLine(text);
        return text;
    }

    public string MethodC1()
    {
        string text = "DecoratorC.MethodC1";
        Console.WriteLine(text);
        return text;
    }
}

public class Client
{
    static void Main(string[] args)
    {
        Console.WriteLine("ComponentA================================");
        ComponentA A = new ComponentA();
        A.StateI = "123";
        A.StateA = "123";
        string tmpA1 = A.MethodI1();
        string tmpA2 = A.MethodA1();

        Console.WriteLine("DecoratorB================================");
        DecoratorB<ComponentA> B = new DecoratorB<ComponentA>();
        B.StateI = "123";
        //B.StateA = "123";                //not possible
        B.StateB = "123";
        string tmpB1 = B.MethodI1();
        //string tmpB2 = B.MethodA1();     //not possible
        string tmpB3 = B.MethodB1();

        Console.WriteLine("DecoratorC================================");
        DecoratorC<DecoratorB<ComponentA>> C = new DecoratorC<DecoratorB<ComponentA>>();
        C.StateI = "123";
        //B.StateA = "123";                //not possible
        //C.StateB = "123";                //not possible
        C.StateC = "123";
        string tmpC1 = C.MethodI1();
        //string tmpC2 = C.MethodA1();     //not possible
        //string tmpC3 = C.MethodB1();     //not possible
        string tmpC4 = C.MethodC1();

        Console.WriteLine("Collection================================");
        List<IComponent> list = new List<IComponent>();
        list.Add(new ComponentA());
        list.Add(new DecoratorB<ComponentA>());
        list.Add(new DecoratorC<DecoratorB<ComponentA>>());

        foreach (IComponent comp in list)
        {
            comp.StateI = "123";
            comp.MethodI1();
        }

        Console.ReadLine();
    }
}

And here is the result from the execution:

Image 5

The main new thing to be noticed is how the Decorator class is defined in (50), as a generics class with type parameter representing reused (decorated) class/object. Also notice in (51) how a component, that needs to have a default constructor, is created.

The rest of the code, particularly Decorators usage is the same.

Note in the class diagram (and code) that we didn’t touch or changed IComponent and ComponentA classes, but just DecoratorB and DecoratorC classes

There is not much new that this version of the Decorator pattern brings to the table. It is even limiting since it relies strictly on the default constructor for the decorated (reused) class. Academically, it is attractive to show a version of Decorator using Generics, but practically there is no gain in new/better features.

Problem with Dynamically selecting type of Component

Generics Decorator has the problem that class type is resolved statically in compile time. So, that makes a problem to create code similar to previously shown for Classic Decorator, that would select the type of component in runtime. It would be maybe possible with a big switch statement, with enumerating statically different possible combinations, but it is not natural and easy as in Classic Decorator.

Dynamic Decorator Pattern – Version 3

Dynamic Decorator pattern exploits C# technology of dynamic objects and Reflection to achieve more than Classic Decorator. This version is enabled specifically by C# technology and might not be possible in some other OO languages that do not support similar technology.

I do not know if anyone independently published a version like this one, but I got the idea for it when I was reading about dynamic Proxy. It come to my mind that similar technology can be used to overcome the limitations of the Classic Decorator pattern.

The key idea is to intercept calls to the unknown method, and redirect calls to the contained object for processing. The contained object will then if it cannot resolve the method call, recursively pass the method call for a resolution to its contained object, etc. We want to emphasize that the main trick here is the passing method calls down the contentment hierarchy, not the inheritance hierarchy.

The code shown here is demo-the-concept level quality, and for usage in production might need some refinement.

Let us show the class diagram and code first, then we will discuss:

Image 6

C#
public interface IComponent
{
    string StateI { get; set; }
    string MethodI1(bool print = true);
}

public class ComponentA : IComponent
{
    public string StateI { get; set; }
    public string StateA { get; set; }

    public ComponentA()
    { }

    public string MethodI1(bool print = true)
    {
        string text = "ComponentA.MethodI1";
        if (print) Console.WriteLine(text);
        return text;
    }

    public string MethodA1()
    {
        string text = "ComponentA.MethodA1";
        Console.WriteLine(text);
        return text;
    }
}

public class DecoratorB : DynamicObject, IComponent          //(60)
{
    public string StateI { get; set; }
    public string StateB { get; set; }

    private dynamic component;

    public DecoratorB(IComponent comp)
    {
        component = comp;
    }

    public override bool TryInvokeMember(                    //(61)
       InvokeMemberBinder binder, object[] args, out object result)
    {
        try
        {
            result = null;
            MethodInfo mInfo =
                component.GetType().GetMethod(binder.Name);  //(62)

            if (mInfo != null)
            {
                result = mInfo.Invoke(component, args);      //(63)
            }
            else
            {
                if (component is DynamicObject)
                {
                    component.TryInvokeMember(               //(64)
                        binder, args, out result);
                }
            }
            return true;
        }
        catch
        {
            result = null;
            return false;
        }
    }

    public override bool TrySetMember(   //(65)
                SetMemberBinder binder, object value)
    {
        try
        {
            PropertyInfo prop = component.GetType().GetProperty(
                binder.Name, BindingFlags.Public | BindingFlags.Instance);

            if (prop != null && prop.CanWrite)
            {
                prop.SetValue(component, value, null);
            }
            else
            {
                if (component is DynamicObject)
                {
                    component.TrySetMember(binder, value);
                }
            }
            return true;
        }
        catch
        {
            return false;
        }
    }

    public override bool TryGetMember(  //(66)
                GetMemberBinder binder, out object result)
    {
        try
        {
            result = null;
            PropertyInfo prop = component.GetType().GetProperty(
                binder.Name, BindingFlags.Public | BindingFlags.Instance);

            if (prop != null && prop.CanWrite)
            {
                result = prop.GetValue(component, null);
            }
            else
            {
                if (component is DynamicObject)
                {
                    component.TryGetMember(binder, out result);
                }
            }
            return true;
        }
        catch
        {
            result = null;
            return false;
        }
    }

    public string MethodI1(bool print = true)
    {
        string baseTmp = component.MethodI1(false);
        string text = baseTmp + " " + "DecoratorB.MethodI1";
        if (print) Console.WriteLine(text);
        return (text);
    }

    public string MethodB1()
    {
        string text = "DecoratorB.MethodB1";
        Console.WriteLine(text);
        return text;
    }
}

public class DecoratorC : DynamicObject, IComponent
{
    //similar to code DecoratorB
    //removed for brevity
}

class Client
{
    static void Main(string[] args)
    {
        Console.WriteLine("ComponentA================================");
        ComponentA A = new ComponentA();
        A.StateI = "III";
        Console.WriteLine(A.StateI);
        A.StateA = "AAA";      // not possible in Classic Decorator
        Console.WriteLine(A.StateA);
        string tmpA1 = A.MethodI1();
        string tmpA2 = A.MethodA1();

        Console.WriteLine("DecoratorB================================");
        dynamic B = new DecoratorB(new ComponentA());
        B.StateI = "III";
        Console.WriteLine(B.StateI);
        B.StateA = "AAA";      // not possible in Classic Decorator
        Console.WriteLine(B.StateA);
        B.StateB = "BBB";
        Console.WriteLine(B.StateB);
        B.StateXXX = "XXX";    // property does not exist, but no exception
        string tmpB1 = B.MethodI1();
        string tmpB2 = B.MethodA1();
        string tmpB3 = B.MethodB1();
        B.MethodXXX();        // method does not exist, but no exception

        Console.WriteLine("DecoratorC================================");
        dynamic C = new DecoratorC(new DecoratorB(new ComponentA())); //(70)
        C.StateI = "III";
        C.StateA = "AAA";     // not possible in Classic Decorator
        Console.WriteLine(C.StateA);
        C.StateB = "BBB";     // not possible in Classic Decorator
        Console.WriteLine(C.StateB);
        C.StateC = "CCC";
        Console.WriteLine(C.StateC);
        C.StateXXX = "XXX";   // property does not exist, but no exception
        string tmpC1 = C.MethodI1();
        string tmpC2 = C.MethodA1();    //(71)
        string tmpC3 = C.MethodB1();    //(72)
        string tmpC4 = C.MethodC1();
        C.MethodXXX();        // method does not exist, but no exception //(73)

        Console.WriteLine("Collection================================");
        List<IComponent> list = new List<IComponent>();
        list.Add(new ComponentA());
        list.Add(new DecoratorB(new ComponentA()));
        list.Add(new DecoratorC(new DecoratorB(new ComponentA())));

        foreach (IComponent iComp in list)
        {
            iComp.StateI = "III";
            Console.WriteLine(iComp.StateI);
            string tmpI1 = iComp.MethodI1();
        }

        Console.ReadLine();
    }
}

Here is the result of sample execution:

Image 7

First of all, notice that in class diagram and code classes IComponent and ComponentA didn’t change. Only code for decorators changed. That shows that this pattern can replace, if needed, the Classic Decorator pattern, without changes to components code.

Let us look at DecoratorB class (60). DecoratorB now inherits from DynamicObject, which brings us the magic of creating methods like (61). In (67), we saved/contained our component as the dynamic object itself, so we can invoke any method on it, which we need in (64).

We will discuss method (61) only since in (65) and (66) similar logic applies. Method (61) is called when no appropriate method name is found in DecoratorB. In (62) with a help of a bit of Reflection magic we want to see if our component has a method of such a name, and if it has we invoke it in (63). If not, then we see if our component is itself a DynamicObject, and if it is, we pass the call to its component (64).

This is a bit of a clever design, with recursively passing method calls to the contained component. I leave it to the reader to study it a bit.

The practical result is seen in (70). Calls that were previously not possible will now work. In particular, in (71) and (72) we make calls down the containment hierarchy, which were not possible with Classic Decorator. The interesting thing is (73) when a call to a method that does not exist is simply ignored.

The Dynamic Decorator pattern is a powerful alternative to the Classic Decorator design. But of course, usage of Dynamic objects brings performance costs to the application.

Conclusion

The Decorator Pattern is a very popular and important pattern. It is frequently used for file and IO streams. Just to mention, C# own IO Streams library is built around Decorator Pattern.

First, we looked at the Classic Decorator pattern, which is described in the literature and in the GoF book. We discussed the problems it has, especially the need to manually implement all methods required to pass functionality through the containment hierarchy. Then we looked at the Generics Decorator pattern version, which is academically interesting but does not bring much new to the table.

In the end, we looked at the Dynamic Decorator pattern, which is the most potent version of the pattern. It is made possible by the C# feature DynamicObject and solves many problems that the Classic Decorator pattern has.

References

History

  • 1st April, 2022: Initial version

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer
Serbia Serbia
Mark Pelf is the pen name of just another Software Engineer from Belgrade, Serbia.
My Blog https://markpelf.com/

Comments and Discussions

 
GeneralMy vote of 5 Pin
LightTempler1-Apr-22 8:04
LightTempler1-Apr-22 8:04 
SO COOL! Smile | :)
Does it help in getting easy understandable code? Maybe not.
But hey! What a great way to remove boring parts of coding! Smile | :)
What comes to my mind: The Try...() functions repeat themself in every further decorator class. Subclassing to make it single located would spoil most of the fun. Maybe a case for extensions?
Thank you for sharing!
LiTe
GeneralMy vote of 5 Pin
Richard MacCutchan1-Apr-22 1:20
mveRichard MacCutchan1-Apr-22 1:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.