The decorator pattern is one of the classics in the Gang of Four Design Patterns book. Let’s explore this pattern first with a C# example.
// Our component to be decorated. It's a coin! (heads == true) public interface ICoin { bool Flip(); } // A generic tracing component. public interface ITracer { void Trace(string message); } // A decorator, which adds tracing to the coin component. The decorator relates // to the component with both inheritance ("is-a") and composition ("has-a"). public sealed class TracedCoin : ICoin { private readonly ICoin inner; private readonly ITracer tracer; // Trace a nice message when we initialize public TracedCoin(ICoin inner, ITracer tracer, string name) { this.inner = inner; this.tracer = tracer; this.tracer.Trace("Flipping " + name + "..."); } // Here's where the "decoration" occurs. The tracing decorator calls the // inner to get the result and adds trace messages before returning. public bool Flip() { bool result = this.inner.Flip(); if (result) { this.tracer.Trace("It was heads!"); } else { this.tracer.Trace("It was tails!"); } return result; } }
This is just your average decorator, which as the code comments say, employs inheritance and composition in the same object. Here is a sample app which uses it:
// Assume we have FairCoin and TextTracer in a common library somewhere. var inner = new FairCoin(); var tracer = new TextTracer(); // Prints 'Flipping MyCoin...' var coin = new TracedCoin(inner, tracer, "MyCoin"); // Prints 'It was heads!' or 'It was tails!' coin.Flip();
There are many advantages to the decorator pattern which I won’t get into here. Instead, read this decorator overview from Alexander Shvets. But if you look at the C# example above, we can see some improvement opportunities. It would be nice to not expose the concrete details of TracedCoin
class, since ultimately we only need to know that it implements ICoin
. Also, if we could provide a more fluent way to build up the decorator chain, it might help the readability. This is especially important if you have multiple layers, since each one is going to need to compose the previous one. Soon we would have some unsightly new Outer(new Middle(new Inner(... ) ) )
monster.
Since C# has extension methods, we can actually achieve all of the above with minimal pain:
// Extension class to hold our decorators. public static class CoinExtensions { // Fluent extension method to build the decorator. public static ICoin WithTracer(this ICoin inner, ITracer tracer, string name) { return new TracedCoin(inner, tracer, name); } // This class is now a private detail and does not need to be exposed. private sealed class TracedCoin : ICoin { // ... as above ... } }
This small amount of extra code gives us an improved API and better encapsulation:
var tracer = new TextTracer(); // Fluent chain, no need to hold on to the 'inner', // and no knowledge of the TracedCoin details. var coin = new FairCoin().WithTracer(tracer, "MyCoin"); coin.Flip();
Okay, I spent several lines extolling the virtues of C#. What about C++? Of course, it is possible to apply the decorator pattern in C++. But it will not necessarily look as “nice” as in C#. Let’s go step by step.
Start with the basic interface definitions (pure abstract classes in C++ by convention):
class ICoin { public: virtual bool flip() = 0; virtual ~ICoin() = default; }; class ITracer { public: virtual void trace(const std::string& message) = 0; virtual ~ITracer() = default; };
So far, so good. Other than the need for virtual destructors, the code looks about the same as in C#.
C# has a garbage collector and generally treats everything (other than value types) as a reference implicitly. In C++ we don’t have GC (at least, I hope not), so we need explicit pointers or references when using a decorated component. References introduce some difficulties around lifetime and scoping, and raw pointers are dangerous with unclear ownership rules. Therefore, we should prefer a smart pointer type for any “interfaces” that we use:
class TracedCoin : public ICoin { public: TracedCoin(std::unique_ptr<ICoin> inner, std::shared_ptr<ITracer> tracer, const std::string& name) : m_inner(std::move(inner)), m_tracer(std::move(tracer)) { std::stringstream ss; ss << "Flipping " << name << "..."; m_tracer->trace(ss.str()); } bool flip() override { bool result = m_inner->flip(); if (result) { m_tracer->trace("It was heads!"); } else { m_tracer->trace("It was tails!"); } return result; } private: std::unique_ptr<wacpp::ICoin> m_inner; std::shared_ptr<wacpp::ITracer> m_tracer; };
In this case it is appropriate to “consume” the ICoin
so we pass a unique_ptr
to transfer ownership to the decorator. The ITracer
, however, is likely to be used by other parties, hence shared_ptr
.
Now the application code:
// Assume FairCoin and TextTracer exist in the library code auto inner = std::make_unique<FairCoin>(); auto tracer = std::make_shared<TextTracer>(); auto coin = std::make_unique<TracedCoin>(std::move(inner), tracer, "MyCoin"); for (int i = 0; i < 10; ++i) { coin->flip(); }
It’s not the worst, but it definitely doesn’t read as well as the final C# result. We could leave it here, but what is the fun in that?!
The first problem we are going to hit is that we have no extension methods in C++. Thus it would seem we are doomed. But wait, there are some cool tricks to build extension method-like functionality in C++. The question is which tricks are appropriate to employ here which won’t get us referred to the IOCCC.
We can start by putting down a desired end state which has some chance of working in C++:
auto coin = make_fair_coin() * with_tracer(tracer, "MyCoin"); for (int i = 0; i < 10; ++i) { coin->flip(); }
Here we have fully encapsulated the FairCoin into a factory function and added a “magic” decorator function with_tracer
. And we’re using a multiplication operator?? But really, I can explain!
We’re using a multiplication operator because decorators are fundamentally product types. (I mean, I had to come up with some justification for a largely arbitrary choice.) But it does have some nice properties, in that it should compose well and even make long chains of decorators possible, just like fluent-style extension methods. Such an operator may have a declaration like this:
// But what to put in the ??? template <typename C> std::unique_ptr<C> operator*(std::unique_ptr<C> decorated, ??? decoration);
This is basically telling us that a decorator creation involves the inner component to be decorated, some yet unknown decoration, and a result that matches the same component interface. But we need to know what to put for the “decoration.” Most generically, it needs to hold all the arguments we’re going to need later to compose the full decorator. This implies we could use a variadic tuple:
template <typename ...Args> struct Decoration { std::tuple<Args...> args; };
So that gives us this declaration:
// Still need to define the ??? template <typename C, typename ...Args> std::unique_ptr<C> operator*(std::unique_ptr<C> decorated, Decoration<Args...>&& decoration) { return ???; }
This seems to be as far as we can go. At this level we know very little about what the final decorated object needs to be. But we could maybe provide a convention. The basic shape should be that you make a decorator by passing the component and the (variable number) arguments. Let’s see if we can ask for a decoration type D
which implements this required operation:
// Add D type template <typename D, typename ...Args> struct Decoration { std::tuple<Args...> args; }; // Final definition requiring D::make_decorator template <typename C, typename D, typename ...Args> std::unique_ptr<C> operator*(std::unique_ptr<C> decorated, Decoration<D, Args...>&& decoration) { return std::apply(std::bind_front(&D::make_decorator, std::move(decorated)), decoration.args); }
So now we expect that the user will give us some type D
with static method make_decorator
to fill in the details. Since the decoration arguments are wrapped in a tuple, we need std::apply
to help unwrap those arguments. But remember that the final decorator constructor signature also includes the inner component C
. This is where std::bind_front
comes to the rescue — we push that argument in place at the front and then pass the rest!
Let’s move on to the decoration pieces:
struct CoinTrace { static std::unique_ptr<ICoin> make_decorator(std::unique_ptr<ICoin> inner, std::shared_ptr<ITracer> tracer, std::string name); }; Decoration<CoinTrace, std::shared_ptr<ITracer>, std::string> with_tracer(std::shared_ptr<ITracer> tracer, std::string&& name);
This is where we materialize the template parameter D
as CoinTrace
for this particular decorator. Everything here looks normal-ish, if we avert our eyes from the Decoration
plumbing fixture. At this point, we might want to ease the burden on the with_...
functions by providing one more convenience in the core decorator library:
template <typename D, typename ...Args> Decoration<D, Args...> with_decoration(Args... args) { return { std::make_tuple(args...) }; }
This is mostly syntactic sugar, but every little bit helps in C++. Now the definitions are nearly trivial:
Decoration<CoinTrace, std::shared_ptr<ITracer>, std::string> with_tracer(std::shared_ptr<ITracer> tracer, std::string&& name) { return with_decoration<CoinTrace>(std::move(tracer), std::move(name)); } std::unique_ptr<ICoin> CoinTrace::make_decorator(std::unique_ptr<ICoin> inner, std::shared_ptr<ITracer> tracer, const std::string& name) { return std::make_unique<TracedCoin>(std::move(inner), std::move(tracer), name); }
At this point, you can totally hide the TracedCoin
class inside the implementation file. The caller only needs to know the base interface and these function declarations.
What’s that, you say? You want to support shared_ptr
decorators too? No problem, we need to add one more overload for our multiplication operator:
// Same as the other one, replacing `unique` with `shared` template <typename C, typename D, typename ...Args> std::shared_ptr<C> operator*(std::shared_ptr<C> decorated, Decoration<D, Args...>&& decoration) { return std::apply(std::bind_front(&D::make_decorator, std::move(decorated)), decoration.args); }
With that, we have a solution that rivals the C# implementation in terms of readability. If you can stomach some of the nonstandard operator usage and all-around C++ wizardry, you might find it worthwhile to implement in your own projects.
For the full C++ code and more sample use cases, check out the associated GitHub repo: writeasync-cpp decorator example