Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Decorator Design Pattern - Lambda Expressions Rework

4.11/5 (4 votes)
29 Dec 2016CPOL4 min read 14.3K  
A Decorator Design Patterns revision obtained adopting a lambda closures approach.

Introduction

Decorator Design Patterns is a well established way to offer an alternative to subclassing. While subclassing adds behavior at compile time, and the added actions affects all instances of the original class; with Decorator it is possible to add additional functionality to a particular object during run time on an effective usage basis without affecting other objects. Going forward the next step may be to revisit the pattern implementation. In this pattern responsibilities can be added or removed at run time. For this purpose I will take the Decorator Pattern and implement it using lambda expressions shorthand, that allows you to write new behavior in the same place you are going to return a decorated class.

Background

We’ll take an easy and fictional example of the Decorator Pattern: describing a typical sandwich assembly. For a classical and already known real world example, you may also see how this pattern is incorporated inth the I/O Streams implementations of both Java and the .NET Framework.

Using the code

This pattern is designed so that multiple decorators are bundled together, in order to add a new functionality each time one or more methods is overridden. Decorator Design Pattern is a structural design pattern. It is also known as wrapper. Fundamentally the pattern foresee two components where the first is abstract and the second is concrete.

First of all we have the interface that defines our component:

C#
public interface ISandwich
{
    string Assemble();
}

Implementing the interface we have a concrete component:

C#
public class BasicSandwich: ISandwich
{
    public string Assemble()
    {
        return "Basic sandwich";
    }
}

Then we decide that we have to decorate our component in different ways. And here is where the Decorator Pattern magic happen. We can add new functionality to an existing object without changing its structure. 

This is the class acting as base class for all decorators for components:

C#
public abstract class SandwichDecorator: ISandwich
{
    protected ISandwich _sandwich;
    
    public SandwichDecorator(ISandwich sandwich)
    {
        _sandwich = sandwich;
    }

    public virtual string Assemble()
    {
        return _sandwich.Assemble();
    }
}

This is a couple of classes which inherit from Decorator class and provide a decorator for components:

C#
public class LettuceSandwich: SandwichDecorator
{
    public LettuceSandwich(ISandwich sandwich): base(sandwich)
    {
    
    }

    public override string Assemble()
    {
        return base.Assemble() + " with lettuce";
    }
}

public class HamSandwich: SandwichDecorator
{

    public HamSandwich(ISandwich sandwich): base(sandwich)
    {

    }

    public override string Assemble()
    {
        return base.Assemble() + " with ham";
    }
}

And finally here is the way to use the new descriptor classes:

C#
ISandwich sandwich = new LettuceSandwich(new BasicSandwich());
string finishedSandwich = sandwich.Assemble(); // Basic Sandwich with lettuce

sandwich = new LettuceSandwich(new HamSandwich(new BasicSandwich()));
finishedSandwich = sandwich.Assemble(); // Basic Sandwich with ham with lettuce

Similarilly, in .NET BufferedStream decorates, or wraps, another stream with buffering capability. Buffering improves performance by reducing round trips to the backing store. Here’s how for example we wrap a FileStream in a 20 KB BufferedStream:

C#
// Write 100K to a file:
File.WriteAllBytes ("myFile.bin", new byte [100000]);
using (FileStream fs = File.OpenRead ("myFile.bin"))
using (BufferedStream bs = new BufferedStream (fs, 20000))
{
    bs.ReadByte();
    Console.WriteLine (fs.Position); // 20000
}

The whole structure depiction of the Descriptor Pattern may be summarized at glance as follow.

Component: ISandwich is an interface containing members that will be implemented by ConcreteClass and Decorator.

ConcreteComponent: BasicSandwich is a class which implements the Component interface.

Decorator: SandwichDecorator is an abstract class which implements the Component interface and contains the reference to a Component instance. This class also acts as base class for all decorators for components.

ConcreteDecorator: LettuceSandwich and HamSandwich are classes which inherits from Decorator class and provides a decorator for components.

Now lets see how we can do the same thing using lambdas, and instead of declaring an abstract class that will provide the template for decorations, we will create the decorator that asks the user for functions with Func<ISandwich, ISandwich> signature used to decorate the component.

C#
public static class SandwichDecorator
{
    public static ISandwich Decorate(ISandwich sandwich, params Func<ISandwich, ISandwich>[] funcs)
    {
        ISandwich res = sandwich;
        foreach (Func<ISandwich, ISandwich> f in funcs)
        {
            res = f.Invoke(res);                
        }
        return res;
    }
}

Ok, now where can we define our decorations? You can add them as static methods in Decorator class or even in the ISandwich interface writing two extension methods:

C#
public class SandwichFactory
{
    private sealed class SandwichImpl : ISandwich
    {
        internal Func<string> _assemble = null;
        public string Assemble()
        {
            return _assemble();
        }
    }

    public ISandwich Create() {
        return new SandwichImpl() { _assemble = () => String.Empty };
    }

    public ISandwich Create(Func<string> bakeFunc) {
        return new SandwichImpl() { _assemble = bakeFunc };
    }
}

public static class SandwichDecorators
{
    public static ISandwich WithLettuce(this ISandwich @this, ISandwich sandwich = null)
    {
        return (new SandwichFactory()).Create(() => (sandwich ?? @this).Assemble() + " with lettuce");
    }
    
    public static ISandwich WithHam(this ISandwich @this, ISandwich sandwich = null)
    {
        return (new SandwichFactory()).Create(() => (sandwich ?? @this).Assemble() + " with ham");
    }
    
}

As you may note I have used a factory pattern in order to create a new ISandwich object instance, this approach is required to workaround a known C# limitation, and this fundamental pattern that belong in every OO developer's vocabulary help us to cope with it in a very elegant way.

Critics says that Decorator Pattern uses a lot of little object of similar type. Trying to reduce poilerplate code, these are the lines that constructs the chain of decorations to be applied:

C#
return (new SandwichFactory()).Create(() => (sandwich ?? @this).Assemble() + " with lettuce");
C#
return (new SandwichFactory()).Create(() => (sandwich ?? @this).Assemble() + " with ham");

Those two lambdas are employed as arguments for the Create method part of the SandwichFactory class. 

And now, here is how this pattern gets to be used:

C#
var mySandwich = new BasicSandwich();
var mySandwich2 = mySandwich.WithLettuce();
var mySandwich3 = mySandwich.WithHam(mySandwich);

var mySandwich4 = SandwichDecorator.Decorate(mySandwich, mySandwich.WithLettuce, mySandwich.WithHam);

Console.WriteLine(mySandwich.Assemble()); // Basic sandwich
Console.WriteLine(mySandwich2.Assemble()); // Basic sandwich with lettuce
Console.WriteLine(mySandwich3.Assemble()); // Basic sandwich with ham

Console.WriteLine(mySandwich4.Assemble()); // Basic sandwich with lettuce with ham

Points of Interest

As you can see, the code got more clear and more concise, and we didn’t use inheritance to build our decorators.

This is just one of the many design pattern that can be improved using lambda expressions. There are more functional features that can be used to improve the solution and many other known patterns as well; in C# functional programming is combined with object oriented programming and you can use a function object as any other object type.

I hope you will enjoy the Decorator Pattern while designing your software. Your valuable feedback, questions, or comments about this article are always welcome.

Bibliography

The fictional Decorator example contained in this article was inspired by this article: Gang of Four – Decorate with Decorator Design Pattern

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)