Design Patterns: Decorator

Why and What

Recently I read the book Dependency Injection. One concept that stood out to me was the Decorator design pattern as it elegantly separates conflicting concerns.

The idea is that a cross cutting concern (like security, performance monitoring, validation, logging, caching, ...) can be handled without imposing additional responsibility on the actual implementation. Let's take a look at an example: Logging.

Logging Camera example

The class under investigation is a 2d game camera. The following interface is stripped down to the bare minimum, but conveys the idea. The interface can easily be extended for further functionality.

class CamInterface {
public:
    virtual ~CamInterface() = default;

    virtual Vector2f getCamOffset() = 0;
    virtual void setCamOffset(Vector2f const& newOffset) = 0;

    // more functions, e.g. zoom, rotate, shake, flash, ...
};

The actual Camera implementation class provides the implementation for those functions. The implementation details are not important here. However it is important, that the Camera implementation class should not know anything about logging. And vice versa, the logging part should ideally not need to know about the underlying implementation details. Both would be a violation of the Single Responsibility Principle (SRP).

The Decorator design pattern solves this in a nice way. First the code, some explanation afterwards:

class LoggingCamera : public CamInterface {
public:
    LoggingCamera(CamInterface& decoratee, LoggerInterface& logger) : 
	  m_decoratee { decoratee }
    , m_logger { logger }
    {
	    m_logger.info("Camera created");
    }

    Vector2f getCamOffset() override
    {
	    m_logger.verbose("getCamOffset");
	    return m_decoratee.getCamOffset();
    }
    void setCamOffset(Vector2f const& newOffset) override
    {
	    m_logger.verbose("setCamOffset");
	    m_decoratee.setCamOffset(newOffset);
    }

    // other functions from the interface

private:
    CamInterface& m_decoratee;
    LoggerInterface& m_logger;
};

The LoggingCamera class inherits from the CamInterface describe above. Its constructor takes two references, one for the CamInterface and one for the LoggerInterface. The references are stored as private members.

For every interface function the LoggingCamera class does two things: create a log entry delegate the call to the decoratee, possibly returning the provided return value.

How to use this?

Logger logger {}; // Implementation class of LoggerInterface

Camera cam {};
LoggingCamera loggingCamera { cam, logger };

Then every user of the Camera can just use the loggingCamera instead of the real camera.

Summary

The Decorator pattern neatly separates the logging concern from the actual Camera implementation. When something in the implementation changes, the LoggingCamera can stay unchanged and if something with the logging changes, the Camera implementation can stay unchanged. This is a very nice separation of concerns.

However there are of course also drawbacks: You need to use Inheritance and all users of the Camera need to only rely on the Interface. Furthermore if a Interface provides a large set of functions, the boilerplate code for the decorator needs to be written multiple times.

Note that because references are stored in the LoggingCamera class, the original camera must live longer than the LoggingCamera.

If you want to see some more examples, please take a look e.g. at Sounds or the StateManager in the JamTemplate.