Skip to content
Carmel Eve By Carmel Eve Software Engineer I
Design patterns in C# - The Decorator Pattern

So, after a long break, this week I've decided to revive my series on design patterns in C#, and move on to the decorator pattern!

To use the decorator pattern, you wrap an object in another object in order to extend behaviour. The objects all implement the same interface, so the decorators can stack on top of one another, extendng the behaviour further. Using this pattern, you are able to extend the behaviour of a single object, rather than extending the behaviour of a class as a whole (by e.g. using subclasses).

I think this will be simpler if we run through a (dinosaur related!) example...

Decorator Example

So, say we have a IGigantosaurus interface, with a single method, Roar:

using System;
using System.Collections.Generic;
using System.Text;

namespace DesignPatterns.Decorator
{
    public interface IGigantosaurus
    {
        string Roar();
    }
}
The Introduction to Rx.NET 2nd Edition (2024) Book, by Ian Griffiths & Lee Campbell, is now available to download for FREE.

And we implement that interface with a class:

using System;
using System.Collections.Generic;
using System.Text;

namespace DesignPatterns.Decorator
{
    public class Gigantosaurus : IGigantosaurus
    {
        public string Roar()
        {
            return "ROAR";
        }
    }
}

What if we then wanted to extend the behaviour of a single Gigantosaurus so that it could roar more loudly? We could wrap the initial gigantosaurus object in a Decorator which looks something like this:


using System;
using System.Collections.Generic;
using System.Text;

namespace DesignPatterns.Decorator
{
    public class LoudGigantosarusDecorator : IGigantosaurus
    {
        protected readonly IGigantosaurus gigantosaurus;

        public LoudGigantosarusDecorator(IGigantosaurus gigantosaurus)
        {
            this.gigantosaurus = gigantosaurus;
        }

        public string Roar()
        {
            return $"{this.gigantosaurus.Roar()} loudly";
        }
    }
}

But, what if even that wasn't enough and we want the ability to make the Gigantosaurus roar even more loudly??

We could then have a second Decorator which extends the behaviour further:

using System;
using System.Collections.Generic;
using System.Text;

namespace DesignPatterns.Decorator
{
    public class ExtraLoudGigantosaurusDecorator : IGigantosaurus
    {
        protected readonly IGigantosaurus gigantosaurus;

        public ExtraLoudGigantosaurusDecorator(IGigantosaurus gigantosaurus)
        {
            this.gigantosaurus = gigantosaurus;
        }

        public string Roar()
        {
            return $"{this.gigantosaurus.Roar()}!";
        }
    }
}

So, if we then build up our wrapped object as follows:

public static void DecoratorExample()
{
    var gigantosaurus = new Gigantosaurus();

    Console.WriteLine(gigantosaurus.Roar());

    var loudGigantosaurus = new LoudGigantosarusDecorator(gigantosaurus);
    var extraLoudGigantosaurus = new ExtraLoudGigantosaurusDecorator(loudGigantosaurus);

    Console.WriteLine(extraLoudGigantosaurus.Roar());
}

Then the first simple Gigantosaurus will produce the output: ROAR.

The best hour you can spend to refine your own data strategy and leverage the latest capabilities on Azure to accelerate your road map.

We then wrap that initial object in both of our decorators to produce the output: ROAR loudly!. We could then even continue to wrap the object in more of our ExtraLoudGigantosaurus decorators to make our gigantosaurus ROAR loudly!!!!!!!!!!!!!!.

In almost all cases, we would expect to call the method on the underlying object, however there are special cases where we might want to, for example, silence our Gigantosaurus:

public class SilentGigantosaurusDecorator : IGigantosaurus
{
    protected readonly IGigantosaurus gigantosaurus;

    public SilentGigantosaurusDecorator(IGigantosaurus gigantosaurus)
    {
        this.gigantosaurus = gigantosaurus;
    }

    public string Roar()
    {
        return "";
    }
}

And there we have it, a pattern for extending/modifying the behaviour of individual objects. It is worth mentioning that the internal method on the wrapped object can be called before or after the extended behaviour within the implementation (though we probably wouldn't want to add exclamation marks before in our example).

This classic example of this pattern in action, is in the CryptoStream class in the .NET framework. The CryptoStream class wraps an ordinary Stream, but extends the behaviour by encrypting the data before writing to the underlying Stream, and decrypts before reading from it.

Thanks for reading! Here's a link to the GitHub repository which contains all the code for this blog series. And watch of for my next blog on design patterns in C#!

Carmel Eve

Software Engineer I

Carmel Eve

Carmel is a software engineer and LinkedIn Learning instructor. She worked at endjin from 2016 to 2021, focused on delivering cloud-first solutions to a variety of problems. These included highly performant serverless architectures, web applications, reporting and insight pipelines, and data analytics engines. After a three-year career break spent travelling around the world, she rejoined endjin in 2024.

Carmel has written many blog posts covering a huge range of topics, including deconstructing Rx operators, agile estimation and planning and mental well-being and managing remote working.

Carmel has released two courses on LinkedIn Learning - one on the Az-204 exam (developing solutions for Microsoft Azure) and one on Azure Data Lake. She has also spoken at NDC, APISpecs, and SQLBits, covering a range of topics from reactive big-data processing to secure Azure architectures.

She is passionate about diversity and inclusivity in tech. She spent two years as a STEM ambassador in her local community and taking part in a local mentorship scheme. Through this work she hopes to be a part of positive change in the industry.

Carmel won "Apprentice Engineer of the Year" at the Computing Rising Star Awards 2019.