Every day design pattern: Decorator

This is the first post in a series of design patterns i use (almost) daily. You will find the other posts at the bottom of this article.

The Decorator pattern

On wikipedia, the decorator pattern is described like so:

In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class.

Personally, i use this pattern to either add functionality to a class from a third party package, or to separate the concerns of a class. The decorator does so by ‘wrapping’ around the original object.

Adding functionality

Lets say i’m sending emails with the symfony Mailer component. You can add a header to an email that tells the email receiver not to send back any automated mails (like an out of office mail).

Since were sending automated mails, we never want to receive those mails. So we want to add that to every single one of our emails. We do this by decorating the MailerInterface. We take the original mailer as a constructor argument, and then we add our own logic.

This is how you’d normally want to use this design pattern. You take the original class in the constructor, and add your own special logic on top of that.

final class DecoratedMailer implements MailerInterface
{
  public function __construct(
    private MailerInterface $inner
  ) {}

  public function send(RawMessage $message, Envelope $envelope = null): void
  {
    // RawMessage doesn't have headers, so we can only do this if we have a `Message`
    if ($message instanceof Message) {
      $message->getHeaders()
        ->addTextHeader('X-Auto-Response-Suppress', 'OOF, DR, RN, NRN, AutoReply');
    }

    $this->inner->send($message, $envelope);
  }
}

Separation of concerns

A decorator can also be used to separate the concerns of a class. Lets say we have the following Api interface, and its implementation. This class does too much, and its hard(er) to maintain or add features to. What if we wanted to not cache certain requests?

interface Api
{
  public function request(string $method, string $url, array $params): string;
}

class MyApi implements Api
{
  public function __construct(
    private Client $guzzle,
    private LoggerInterface $logger,
    private CacheInterface $cache,
    private string $apiKey
  ) {}

  public function request(string $method, string $url, array $params): string
  {
    $cacheKey = $method . $url . http_build_query($params);
    $cachedResult = $this->cache->get($cacheKey);
    if ($cachedResult !== null) {
      return $cachedResult;
    }

    $this->logger->info(sprintf(
      'Requesting %s - %s',
      $method, $url
    ));

    $params[RequestOptions::HEADERS]['api-key'] = $this->apiKey;

    $result = $this->guzzle->request($method, $url, $params);
    $body = (string) $result->getBody();
    $this->logger->info(sprintf(
      'Response: "%s"',
      $body
    ));

    $this->cache->set($cacheKey, $body);

    return $body;
  }
}

Lets split it up into multiple classes, using the decorator pattern. We turn this into three classes, the decorator that cares about cache, the decorator that cares about logging, an the actual class. Now we can easily remove cache for a specific request. Testing also becomes easier. The test for the CachingApi now only needs to check that items are properly added to cache, or fetched form it.

class CachingApi implements Api
{
  public function __construct(
    private Api $inner,
    private CacheInterface $cache
  ) {}

  public function request(string $method, string $url, array $params): string
  {
    $cacheKey = $method . $url . http_build_query($params);
    $cachedResult = $this->cache->get($cacheKey);
    if ($cachedResult !== null) {
      return $cachedResult;
    }

    $result = $this->inner->request(string $method, string $url, array $params);

    $this->cache->set($cacheKey, $result);

    return $result;
}

class LoggingApi implements Api
{
  public function __construct(
    private Api $inner,
    private LoggerInterface $logger
  ) {}

  public function request(string $method, string $url, array $params): string
  {
    $this->logger->info(sprintf(
      'Requesting %s - %s',
      $method, $url
    ));

    $result = $this->inner->request(string $method, string $url, array $params);

    $this->logger->info(sprintf(
      'Response: "%s"',
      $result
    ));

    return $result;
  
}

class MyApi implements Api
{
  public function __construct(
    private Client $guzzle,
    private string $apiKey
  ) {}

  public function request(string $method, string $url, array $params): string
  {
    $params[RequestOptions::HEADERS]['api-key'] = $this->apiKey;
    $result = $this->guzzle->request($method, $url, $params);

    return(string) $result->getBody();
  }
}

No interface, no problem

The examples so far have used interfaces. Sometimes the class we wish to decorate does not have an interface. In that case we need to extend the original class. You now have the option to either call the parent directly, or inject the class in the constructor. Personally i would go with injecting the class in the constructor, as this allows you to have multiple decorators on top of each other.

The problem

So i lied a little bit, as there can be a problem when using a decorator when you extend a class. And that is with fluid interfaces. A fluid interface is a class that returns itself on (almost) every method call. Like the following Request class.

We could decorate this no problem, and do the following.

class Request
{
  public function setUrl(string $url)
  {
    $this->url = $url;
    return $this;
  }

  public function setMethod(string $method)
  {
    $this->method = $method;
    return $this;
  }
}

class EchoRequest extends Request
{
  public function setUrl(string $url)
  {
    echo $url;
    $this->inner->setUrl($url);
    return $this;
  }

  public function setMethod(string $method)
  {
    echo $method;
    $this->inner->setMethod($method);
    return $this;
  }
}

But, what happens if the request class gets updated, and a the following method gets added. Suddenly when you call setParams on the decorated class, you are working with the non decorated version. So be careful when using the decorator pattern in combination with fluid interfaces.

class Request
{
  //...
  public function setParams(array $params)
  {
    $this->params = $params;
    return $this;
  }
}

The takeaway

The decorator patterns allows you to add additional functionality to a class, or it can help in splitting up the responsibilities of a class. You may want to be careful when decorating an object that has a fluid interface, as you can suddenly use the non decorated class.

  1. Decorator
  2. Adapter
  3. Factory
  4. Builder
Avatar
Gert de Pagter
Software Engineer

My interests include software development, math and magic.