© Dmitri Nesteruk 2020
D. NesterukDesign Patterns in .NET Core 3https://doi.org/10.1007/978-1-4842-6180-4_10

10. Decorator

Dmitri Nesteruk1  
(1)
St. Petersburg, c.St-Petersburg, Russia
 

Suppose you’re working with a class your colleague wrote, and you want to extend that class’ functionality. How would you do it, without modifying the original code? Well, one approach is inheritance: you make a derived class, add the functionality you need, maybe even override something, and you’re good to go.

Right, except this doesn’t always work, and there are many reasons why. The most common reason is that you cannot inherit the class – either because your target class needs to inherit something else (and multiple inheritance is impossible) or because the class you want to extend is sealed.

The Decorator pattern allows us to enhance existing types without either modifying the original types (Open-Closed Principle) or causing an explosion of the number of derived types.

Custom String Builder

Suppose you’re into code generation and you want to extend StringBuilder in order to offer additional utility methods such as supporting indentation or scopes or whatever code generation functionality makes sense. It would be nice to simply inherit from StringBuilder, but it’s sealed (for security reasons). Also, since you might want to store the current indentation level (say, to provide Indent()/Unindent() methods), you cannot simply go ahead and use extension methods, since those are stateless.1

So the solution is to create a Decorator: a brand-new class that aggregates a StringBuilder but also stores and exposes the same members as StringBuilder did, and a few more. From the outset, the class can look as follows:
public class CodeBuilder
{
  private StringBuilder builder = new StringBuilder();
  private int indentLevel = 0;
  public CodeBuilder Indent()
  {
    indentLevel++;
    return this;
  }
}

As you can see, we have both the “underlying” StringBuilder and some additional members related to the extended functionality. What we need to do now is expose the members of StringBuilder as members of CodeBuilder , delegating the calls. StringBuilder has a very large API, so doing this by hand is unreasonable: instead, what you would do is use code generation (e.g., ReSharper’s Generate | Delegated members) to automatically create the necessary API.

This operation can be applied to every single member of StringBuilder and will generate the following signatures:
public class CodeBuilder
{
  public StringBuilder Append(string value)
  {
    return builder.Append(value);
  }
  public StringBuilder AppendLine()
  {
    return builder.AppendLine();
  }
  // other generated members omitted
}
This might seem great at first glance, but in actual fact, the implementation is incorrect. Remember, StringBuilder exposes a fluent API in order to be able to write things like
myBuilder.Append("Hello").AppendLine(" World");
In other words, it provides a fluent interface. But our decorator does not! For example, it won’t let us write myBuilder.Append("x").Indent() because the result of Append() , as generated by ReSharper, is a StringBuilder which doesn’t have an Indent() member. That’s right – ReSharper does not know that we want a proper fluent interface. What you want is the fluent calls in CodeBuilder to appear as
public class CodeBuilder
{
  public CodeBuilder Append(char value, int repeatCount)
  {
    builder.Append(value, repeatCount);
    return this; // return a CodeBuilder, not a StringBuilder
  }
  ...
}

This is something that you’d need to fix by hand or possibly through regular expressions. This modification, when applied to every single call that’s delegated to StringBuilder, would allow us to chain StringBuilder’s calls together with our unique, CodeBuilder-specific ones.

Adapter-Decorator

You can also have a decorator that acts as an adapter. For example, suppose we want to take the CodeBuilder from earlier, but we want it to start acting as a string. Perhaps we want to take a CodeBuilder and stick it into an API which expects our object to implement the = operator for assigning from a string and a += operator for appending additional strings. Can we adapt CodeBuilder to these requirements? We sure can; all we have to do is add the appropriate functionality:
public static implicit operator CodeBuilder(string s)
{
  var cb = new CodeBuilder();
  cb.sb.Append(s);
  return cb;
}
public static CodeBuilder operator +(CodeBuilder cb, string s)
{
  cb.Append(s);
  return cb;
}
With this implementation, we can now start working with a CodeBuilder as if it were a string:
CodeBuilder cb = "hello";
cb += " world";
WriteLine(cb); // prints "hello world"

Curiously enough, the second line in the preceding code will work even if we didn’t implement operator + explicitly. Why? You figure it out!

Multiple Inheritance with Interfaces

In addition to extending sealed classes , the Decorator also shows up when you want to have multiple base classes… which of course you cannot have because C# does not support multiple inheritance. For example, suppose you have a Dragon that’s both a Bird and a Lizard. It would make sense to write something like:
public class Bird
{
  public void Fly() { ... }
}
public class Lizard
{
  public void Crawl() { ... }
}
public class Dragon : Bird, Lizard {} // cannot do this!
Sadly, this is impossible, so what do you do? Well, you extract interfaces from both Bird and Lizard:
public interface IBird
{
  void Fly();
}
public interface ILizard
{
  void Crawl();
}
Then you make a Dragon class that implements these interfaces, aggregates instances of Bird and Lizard, and delegates the calls:
public class Dragon: IBird, ILizard
{
  private readonly IBird bird;
  private readonly ILizard lizard;
  public Dragon(IBird bird, ILizard lizard)
  {
    this.bird = bird;
    this.lizard = lizard;
  }
  public void Crawl()
  {
    lizard.Crawl();
  }
  public void Fly()
  {
    bird.Fly();
  }
}

You’ll notice that there are two options here: either you initialize the default instances of Bird and Lizard right inside the class or you offer the client more flexibility by taking both of those objects in the constructor. This would allow you to construct more sophisticated IBird/ILizard classes and make a dragon out of them. Also, this approach automatically supports constructor injection, should you go the IoC route.

One interesting problem with the decorator is the “diamond inheritance” problem of C++. Suppose a dragon crawls only until it’s 10 years old, and from then on, it only flies. In this case, you’d have both the Bird and Lizard classes have an Age property with independent implementations:
public interface ICreature
{
  int Age { get; set; }
}
public interface IBird : ICreature
{
  void Fly();
}
public interface ILizard : ICreature
{
  void Crawl();
}
public class Bird : IBird
{
  public int Age { get; set; }
  public void Fly()
  {
    if (Age >= 10)
      WriteLine("I am flying!");
  }
}
public class Lizard : ILizard
{
  public int Age { get; set; }
  public void Crawl()
  {
    if (Age < 10)
      WriteLine("I am crawling!");
  }
}
Notice that we’ve had to introduce a new interface ICreature just so we could expose the Age as part of both the IBird and ILizard interfaces . The real problem here is the implementation of the Dragon class, because if you use the code generation features of ReSharper or similar tool, you will simply get
public class Dragon : IBird, ILizard
{
  ...
  public int Age { get; set; }
}
This once again shows that generated code isn’t always what you want. Remember, both Bird.Fly() and Lizard.Crawl() have their own implementations of Age, and those implementations need to be kept consistent in order for those methods to operate correctly. This means that the correct implementation of Dragon.Age is the following:
public int Age
{
  get => bird.Age;
  set => bird.Age = lizard.Age = value;
}
Notice that our setter assigns both, whereas the getter simply uses the underlying bird – this choice is arbitrary; we could have easily taken the lizard’s age instead. The setter ensures consistency, so in theory, both values would always be equal… except during initialization, a place we haven’t taken care of yet. A lazy man’s solution to this problem would be to redefine the Dragon constructor thus:
public Dragon(IBird bird, ILizard lizard)
{
  this.bird = bird;
  this.lizard = lizard;
  bird.Age = lizard.Age;
}

As you can see, building a decorator is generally easy, except for two nuances: the difficulties in preserving a fluent interface and the challenge of diamond inheritance. I have demonstrated here how to solve both of these problems.

Multiple Inheritance with Default Interface Members

The collision between the Age properties of Bird and Lizard can be partially mitigated with C# 8’s default interface members. While they do not give us “proper,” C++-style multiple inheritance, they give us enough to go by.

First of all, we implement a base interface for a creature:
public interface ICreature
{
  int Age { get; set; }
}
This step is essential, because now we can define interfaces IBird and ILizard that have default method implementations that actually make use of the property:
public interface IBird : ICreature
{
  void Fly()
  {
    if (Age >= 10)
      WriteLine("I am flying");
  }
}
public interface ILizard : ICreature
{
  void Crawl()
  {
    if (Age < 10)
      WriteLine("I am crawling!");
  }
}
Finally, we can make a class that implements both of these interfaces. Of course, this class has to provide an implementation of the Age property, since no interface is able to do so:
public class Dragon : IBird, ILizard
{
  public int Age { get; set; }
}
And now we have a class that inherits the behavior of two interfaces. The only caveat is that explicit casts are required to actually make use of those behaviors:
var d = new Dragon {Age = 5};
if (d is IBird bird)
  bird.Fly();
if (d is ILizard lizard)
  lizard.Crawl();

Dynamic Decorator Composition

Of course, as soon as we start building decorators over existing types, we come to the question of decorator composition , that is, whether or not it’s possible to decorate a decorator with another decorator. I certainly hope it’s possible – decorators should be flexible enough to do this!

For our scenario, let’s imagine that we have an abstract base class called Shape with a single member called AsString() that returns a string describing this shape (I’m deliberately avoiding ToString() here):
public abstract class Shape
{
  public virtual string AsString() => string.Empty;
}

I chose to make Shape an abstract class with a default, no-op implementation. We could equally use an IShape interface for this example.

We can now define a concrete shape like, say, a circle or a square:
public sealed class Circle : Shape
{
  private float radius;
  public Circle() : this(0)
  {}
  public Circle(float radius)
  {
    this.radius = radius;
  }
  public void Resize(float factor)
  {
    radius *= factor;
  }
  public override string AsString() => $"A circle of radius {radius}";
}
// similar implementation of Square with “side” member omitted
I deliberately made Circle and similar classes sealed so we cannot go ahead and simply inherit from them. Instead, we are once again going to build decorators: this time, we’ll build two of them – one for adding color to a shape…
public class ColoredShape : Shape
{
  private readonly Shape shape;
  private readonly string color;
  public ColoredShape(Shape shape, string color)
  {
    this.shape = shape;
    this.color = color;
  }
  public override string AsString()
    => $"{shape.AsString()} has the color {color}";
}
and another to give a shape transparency:
public class TransparentShape : Shape
{
  private readonly Shape shape;
  private readonly float transparency;
  public TransparentShape(Shape shape, float transparency)
  {
    this.shape = shape;
    this.transparency = transparency;
  }
  public override string AsString() =>
    $"{shape.AsString()} has {transparency * 100.0f}% transparency";
}
As you can see, both of these decorators inherit from the abstract Shape class, so they are themselves Shapes and they decorate other Shapes by taking them in the constructor. This allows us to use them together, for example:
var circle = new Circle(2);
WriteLine(circle.AsString());
// A circle of radius 2
var redSquare = new ColoredShape(circle, "red");
WriteLine(redSquare.AsString());
// A circle of radius 2 has the color red
var redHalfTransparentSquare = new TransparentShape(redSquare, 0.5f);
WriteLine(redHalfTransparentSquare.AsString());
// A circle of radius 2 has the color red and has 50% transparency

As you can see, the decorators can be applied to other Shapes in any order you wish, preserving consistent output of the AsString() method. One thing they do not guard against is cyclic repetition: you can construct a ColoredShape(ColoredShape(Square)) and the system will not complain; we could not detect this situation either, even if we wanted to.

So this is the dynamic decorator implementation: the reason why we call it dynamic is because these decorators can be constructed at runtime, objects wrapping objects as layers of an onion. It is, on the one hand, very convenient, but on the other hand, you lose all type information as you decorate the object. For example, a decorated Circle no longer has access to its Resize() member:
var redSquare = new ColoredShape(circle, "red");
redCircle.Resize(2); // oops!

This problem is impossible to solve: since ColoredShape takes a Shape, the only way to allow resizing is to add Resize() to Shape itself, but this operation might not make sense for all shapes. This is a limitation of the dynamic decorator.

Static Decorator Composition

When you are given a dynamically decorated ColorShape , there’s no way to tell whether this shape is a circle, square, or something else without looking at the output of AsString() . So how would you “bake in” the underlying type of the decorated objects into the type of the object you have? Turns out you can do so with generics.

The idea is simple: our decorator, say ColoredShape , takes a generic argument that specifies what type of object it’s decorating. Naturally, that object has to be a Shape, and since we’re aggregating it, it’s also going to need a constructor:
public class ColoredShape<T> : Shape
  where T : Shape, new()
{
  private readonly string color;
  private readonly T shape = new T();
  public ColoredShape() : this("black") {}
  public ColoredShape(string color) { this.color = color; }
  public override string AsString() =>
    return $"{shape.AsString()} has the color {color}";
}

Okay, so what’s going on here? We have a new ColoredShape that’s generic; it takes a T that’s supposed to inherit a Shape. Internally, it stores an instance of T as well as color information. We’ve provided two constructors for flexibility: since C#, unlike C++, doesn’t support constructor forwarding, the default constructor is going to be useful for composition (see, we have the new() requirement).

We can now provide a similar implementation of TransparentShape<T>, and armed with both, we can now build static decorators of the following form:
var blueCircle = new ColoredShape<Circle>("blue");
WriteLine(blueCircle.AsString());
// A circle of radius 0 has the color blue
var blackHalfSquare = new TransparentShape<ColoredShape<Square>>(0.4f);
WriteLine(blackHalfSquare.AsString());
// A square with side 0 has the color black and has transparency 40
This static approach has certain advantages and disadvantages. The advantage is that we preserve the type information : given a Shape we can tell that the shape is a ColoredShape<Circle> and perhaps we can act on this information somehow. Sadly, this approach has plenty of disadvantages:
  • Notice how the radius/side values in the preceding example are both zero. This is because we cannot initialize those values in the constructor: C# does not have constructor forwarding.

  • We still don’t have access to the underlying members; for example, blueCircle.Resize() is still not legal.

  • These sorts of decorators cannot be composed at runtime.

All in all, in the absence of CRTP and mixin inheritance,2 the uses for static decorators in C# are very, very limited.

Functional Decorator

A functional decorator is a natural consequence of functional composition. If we can compose functions, we can equally wrap functions with other functions in order, for example, to provide before-and-after functionality such as logging.

Here’s a very simple implementation. Imagine you have some work that needs to be done:
let doWork() =
  printfn "Doing some work"
We can now create a decorator function (a functional decorator!) that, given any function, measures how long it takes to execute:
let logger work name =
  let sw = Stopwatch.StartNew()
  printfn "%s %s" "Entering method" name
  work()
  sw.Stop()
  printfn "Exiting method %s; %fs elapsed" name sw.Elapsed.TotalSeconds
We can now use this wrapper around doWork, replacing a unit -> unit function with one with the same interface but which also performs some measurements:
let loggedWork() = logger doWork "doWork"
loggedWork()
// Entering method doWork
// Doing some work
// Exiting method doWork; 0.097824s elapsed

Pay attention to the round brackets in this example: it might be tempting to remove them, but that would drastically alter the types of data structures. Remember, any let x = ... construct will always evaluate to a variable (possibly of a unit type!) instead of a parameterless function unless you add an empty argument list.

There are a couple of catches in this implementation. For one, doWork does not return a value; if it did, we would have to cache it in a type-independent manner, something that’s possible to implement in C++ but extremely difficult to do in any .NET language. Another issue is that we have no way of determining the name of the wrapped function , so we end up passing it as a separate argument – not an ideal solution!

Summary

A decorator gives a class additional functionality while adhering to the OCP and mitigating issues related to sealed classes and multiple inheritance. Its crucial aspect is composability: several decorators can be applied to an object in any order. We’ve looked at the following types of decorators:
  • Dynamic decorators which can store references to the decorated objects and provide dynamic (runtime) composability.

  • Static decorators which preserve the information about the type of the objects involved in the decoration; these are of limited use since they do not expose underlying objects’ members, nor do they allow us to efficiently compose constructor calls.

In both of the cases, we completely ignored the issues related to cyclic use: nothing in the API prevents applying the same static or dynamic decorator more than once.

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

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