Neste artigo
O Padrão de Projeto Decorator permite acrescentar responsabilidades a um objeto em tempo de execução, sem alterar a classe dele e sem usar herança. Ele faz isso “embrulhando” o objeto original em outro objeto que implementa a mesma interface: o decorador recebe a chamada, repassa para o objeto embrulhado e acrescenta o seu comportamento antes ou depois.
Como o decorador tem a mesma interface do objeto que ele embrulha, quem usa não percebe a diferença. E como um decorador pode embrulhar outro decorador, dá para empilhar quantos você precisar. É por isso que esse padrão também é conhecido como Wrapper.

Observe no diagrama os quatro participantes do padrão:
- Component (Notifier): a interface comum ao objeto original e aos decoradores.
- ConcreteComponent (EmailNotifier): o objeto que vai receber as responsabilidades novas.
- Decorator (NotifierDecorator): guarda uma referência a um Component e repassa as chamadas para ele.
- ConcreteDecorator (SmsNotifier e SlackNotifier): acrescenta a responsabilidade.
Qual problema o Decorator resolve?
Imagine um sistema que avisa a equipe quando um deploy termina. No começo o aviso vai por e-mail, e uma classe resolve. Depois alguém pede SMS. Depois, Slack. E cada pessoa quer uma combinação diferente.
Se você resolver isso com herança, vai precisar de uma classe para cada combinação: e-mail com SMS, e-mail com Slack, e-mail com SMS e Slack. Com dois canais opcionais são 4 combinações; com três, 8; e cada canal novo dobra esse número. Com o Decorator, cada canal vira uma classe pequena, e a combinação é montada na hora, de acordo com a preferência de cada usuário.
Exemplo do padrão Decorator em PHP
Vamos para o código. O contrato é simples: todo notificador sabe enviar uma mensagem.
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Structural\Decorator;
interface Notifier
{
public function send(string $message): void;
}
O EmailNotifier é o componente concreto. Ele faz o trabalho básico e não sabe que pode ser decorado.
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Structural\Decorator;
final class EmailNotifier implements Notifier
{
private readonly string $email;
public function __construct(string $email)
{
$this->email = $email;
}
public function send(string $message): void
{
printf("E-mail to %s: %s\n", $this->email, $message);
}
}
Agora vem a peça central do padrão. O NotifierDecorator implementa a interface Notifier e, ao mesmo tempo, guarda um Notifier dentro dele. A única coisa que ele faz é repassar a chamada.
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Structural\Decorator;
abstract class NotifierDecorator implements Notifier
{
private readonly Notifier $notifier;
public function __construct(Notifier $notifier)
{
$this->notifier = $notifier;
}
public function send(string $message): void
{
$this->notifier->send($message);
}
}
Cada decorador concreto estende essa classe base, chama o parent::send() para manter o que já existia e acrescenta o seu canal.
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Structural\Decorator;
final class SmsNotifier extends NotifierDecorator
{
private readonly string $phone;
public function __construct(Notifier $notifier, string $phone)
{
parent::__construct($notifier);
$this->phone = $phone;
}
public function send(string $message): void
{
parent::send($message);
printf("SMS to %s: %s\n", $this->phone, $message);
}
}
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Structural\Decorator;
final class SlackNotifier extends NotifierDecorator
{
private readonly string $channel;
public function __construct(Notifier $notifier, string $channel)
{
parent::__construct($notifier);
$this->channel = $channel;
}
public function send(string $message): void
{
parent::send($message);
printf("Slack to %s: %s\n", $this->channel, $message);
}
}
Repare que nenhuma dessas classes conhece as outras. O SmsNotifier não sabe se está embrulhando um e-mail, um Slack ou outro SMS. Ele só sabe que tem um Notifier nas mãos.
Testando o exemplo
Nos testes dá para ver o padrão funcionando em três situações: sem nenhum decorador, com dois decoradores empilhados e com os decoradores escolhidos em tempo de execução, a partir das preferências do usuário.
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Tests\Structural\Decorator;
use Growthdev\DesignPatterns\Structural\Decorator\EmailNotifier;
use Growthdev\DesignPatterns\Structural\Decorator\Notifier;
use Growthdev\DesignPatterns\Structural\Decorator\SlackNotifier;
use Growthdev\DesignPatterns\Structural\Decorator\SmsNotifier;
use PHPUnit\Framework\TestCase;
final class DecoratorTest extends TestCase
{
public function testExpectOnlyEmailWithoutDecorators(): void
{
$notifier = new EmailNotifier('dev@example.com');
$notifier->send('Deploy finished');
$this->expectOutputString("E-mail to dev@example.com: Deploy finished\n");
}
public function testExpectEachDecoratorToAddOneChannel(): void
{
$notifier = new SlackNotifier(
new SmsNotifier(new EmailNotifier('dev@example.com'), '+55 11 90000-0000'),
'#deploys'
);
$notifier->send('Deploy finished');
$this->expectOutputString(
"E-mail to dev@example.com: Deploy finished\n"
. "SMS to +55 11 90000-0000: Deploy finished\n"
. "Slack to #deploys: Deploy finished\n"
);
}
public function testExpectDecoratorsToBeChosenAtRuntime(): void
{
$preferences = ['slack' => '#deploys'];
$notifier = new EmailNotifier('dev@example.com');
if (isset($preferences['sms'])) {
$notifier = new SmsNotifier($notifier, $preferences['sms']);
}
if (isset($preferences['slack'])) {
$notifier = new SlackNotifier($notifier, $preferences['slack']);
}
$this->assertInstanceOf(Notifier::class, $notifier);
$notifier->send('Deploy finished');
$this->expectOutputString(
"E-mail to dev@example.com: Deploy finished\n"
. "Slack to #deploys: Deploy finished\n"
);
}
}
No segundo teste, a ordem da saída é e-mail, SMS e Slack. Isso acontece porque cada decorador chama primeiro o objeto que está embrulhando e só depois faz a sua parte. Se você inverter a ordem em que embrulha, a ordem do envio muda junto. No terceiro teste, o usuário só pediu Slack, então o SmsNotifier nem chega a ser criado.
Quando usar o padrão Decorator?
- Quando as responsabilidades são opcionais e podem ser combinadas de várias formas.
- Quando você não pode ou não quer mexer na classe original: ela é
final, vem de uma biblioteca de terceiros ou já está bem testada. - Quando a responsabilidade precisa ser ligada e desligada em tempo de execução.
No ecossistema PHP você encontra esse padrão com frequência. O Symfony tem a decoração de serviços no container de injeção de dependência, e o pacote guzzlehttp/psr7 traz decoradores de stream prontos.
Cuidados ao usar o Decorator
- Muitas camadas pequenas dificultam a depuração: o erro pode estar em qualquer uma delas.
- A ordem dos decoradores muda o resultado, como você viu no teste.
- O objeto decorado não é o objeto original. Código que depende da classe concreta, como um
instanceof EmailNotifier, deixa de funcionar quando o objeto está embrulhado.
Decorator, Proxy e Adapter: qual a diferença?
Os três embrulham um objeto, e é por isso que são confundidos. A diferença está na intenção de cada um:
| Padrão | Interface que expõe | Intenção |
|---|---|---|
| Decorator | A mesma do objeto embrulhado | Acrescentar comportamento |
| Proxy | A mesma do objeto embrulhado | Controlar o acesso ao objeto |
| Adapter | Uma interface diferente | Fazer duas interfaces incompatíveis trabalharem juntas |
Esse é mais um padrão estrutural do livro GoF. Para conhecer os outros, veja o artigo Resumo dos Padrões de Projetos.
O código completo deste exemplo, com os testes, está no meu GitHub, na pasta src/Structural/Decorator do repositório growthdev-repo/design-patterns.
Veja também: o resumo dos 23 padrões de projeto GoF e os outros padrões estruturais já publicados: Adapter, Bridge, Composite, Facade, Flyweight, Proxy.
Confiança Sempre!!!
Fontes:
- [1] GAMMA, Erich et al. Padrões de Projeto: Soluções reutilizáveis de software orientado a objetos.
- [2] FREEMAN, Eric; ROBSON, Elisabeth; BATES, Bert; SIERRA, Kathy. Head First Design Patterns.

Continue a conversa