Advertisement
  1. Code
  2. Design Patterns

Padrões de design: O padrão Decorator

Scroll to top
Read Time: 6 min
This post is part of a series called Design Patterns in PHP.
Design Patterns: The Adapter Pattern
Design Patterns: The Strategy Pattern

() translation by (you can also view the original English article)

Previamente nestas séries, exploramos ambos os padrões de design facade e adapter. Usando facade(fachada), nós podemos simplificar grandes sistemas, e através da implementação do adapter, nós podemos manter-nos seguros enquanto trabalhamos com classes de API externas. Agora nós vamos cobrir o padrão de design decorator, o qual também pertence a categoria dos padrões de estrutura.

Nós podemos usar o padrão decorator, quando simplesmente queremos adicionar alguma responsabilidade a nossa classe. Este padrão de design é uma boa alternativa a funcionalidade de subclasses para estender funcionalidade com algumas vantagens adicionais.

Problema

Se estás confuso e pensas que nós podemos alcançar a mesma funcionalidade com a subclasse, deixa-me demonstrar alguns exemplos de código, que irão remover essa confusão e farão adorares o padrão decorator.

Eu vou pegar no exemplo de uma classe a qual é responsável por gerar conteúdo para um email. No próximo bloco de código, como podes ver, esta classe funciona muito bem para gerar conteúdo de email sem qualquer modificação.

1
class eMailBody {
2
private $header = 'This is email header';
3
  private $footer = 'This is email Footer';
4
	public $body = '';
5
6
	public function loadBody() {
7
		$this->body .= "This is Main Email body.<br />";
8
	}
9
}

Nós sabemos que o Natal está a chegar, e vamos dizer que eu quero agradar ao meu leitor com uma mensagem no próximo email de newsletter. Assim tenho que adicionar uma mensagem no corpo do email com uma imagem que fique bem.

Por causa disto eu posso diretamente editar a minha classe email, o que eu não quero realmente fazer. Assim eu posso implementar herança para alcançar o mesmo efeito. Eu crio uma classe filho separada da classe principal do corpo do email:

1
class christmasEmail extends eMailBody {
2
public function loadBody() {
3
		parent::loadBody();
4
		$this->body .= "Added Content for Xmas<br />";
5
	}
6
}
7
8
$christmasEmail = new christmasEmail();
9
$christmasEmail->loadBody();
10
echo $christmasEmail->body;

Assim, eu tenho o meu código pronto, e após alguns dias, eu quero ler o email com saudações de Novo Ano. Podemos utilizar o mesmo método que fizemos para o Natal.

1
class newYearEmail extends eMailBody {
2
public function loadBody() {
3
		parent::loadBody();
4
		$this->body .= "Added Content for New Year<br />";
5
	}
6
}
7
8
$newYearEmail = new newYearEmail();
9
$newYearEmail->loadBody();
10
echo $newYearEmail->body;

Isto é feito de forma suave, sem qualquer problemas. Agora vamos dizer que eu esqueci-me de felicitar os meus visitantes nas duas ocasiões (Natal e Ano Novo) e eu quero mandar ambas as felicitações num email, sem modificar qualquer código na classe base. 

A tua mente fica cheia imediatamente, com a seguinte questão: Irá subclasses e herança ajudar? Eu seria a favor de seguir este caminho, mas iriamos necessitar de usar extra/desnecessário código para alcançar isto. Podemos usar traits que nos permite implementar algo semelhante a múltipla herança.

Solução

O problema que discutimos na secção anterior pode ser resolvido através da implementação do padrão decorator. 

De acordo com aWikipedia:

O padrão decorator (também conhecido por wrapper, uma alternativa de nome partilhada com o padrão Adapter), o qual é um padrão de design que permite a adição de comportamento a um objeto individual, tanto estaticamente ou dinamicamente, sem afetar o comportamento de outros objetos da mesma classe.

Na secção acima nós vimos que podemos estender funcionalidades/comportamento usando uma subclasse, mas quando se trata de adicionar múltiplas funcionalidades/comportamentos, torna-se complexo e demorado. É nestes casos que devemos utilizar o padrão decorator.

Interface

1
interface eMailBody {
2
public function loadBody();
3
}

Esta é uma simples interface para ter a certeza que qualquer classe tem que implementar os métodos necessários. 

Classe principal email

1
class eMail implements eMailBody {
2
public function loadBody() {
3
		echo "This is Main Email body.<br />";
4
	} 
5
}

Esta é a classe principal a qual está a gerar o corpo por defeito de um email, o qual é geralmente usado para enviar emails. O que necessitamos, contudo, é de modificar o conteúdo do corpo baseando em algumas ocasiões, mas sem alterar a classe principal de email.

Decorator principal

1
abstract class emailBodyDecorator implements eMailBody {
2
	
3
	protected $emailBody;
4
	
5
	public function __construct(eMailBody $emailBody) {
6
		$this->emailBody = $emailBody;
7
	}
8
	
9
	abstract public function loadBody();
10
	
11
} 

Esta é a nossa classe principal decorator, o qual armazena a referência para a nossa classe principal email e modifica o comportamento como necessário. Aqui nós modificamos o nosso método abstrato, loadBody, o qual o sub decorator precisa de implementar para mudar de comportamento.

Sub Decorator

1
class christmasEmailBody extends emailBodyDecorator {
2
	
3
	public function loadBody() {
4
		
5
		echo 'This is Extra Content for Christmas<br />';
6
		$this->emailBody->loadBody();
7
		
8
	}
9
	
10
}
11
12
class newYearEmailBody extends emailBodyDecorator {
13
14
	public function loadBody() {
15
		
16
		echo 'This is Extra Content for New Year.<br />';
17
		$this->emailBody->loadBody();
18
		
19
	}
20
21
}

Aqui nós criamos duas subclasses do decorator principal, o qual atualmente faz com que haja alteração de comportamento para a classe principal de email.

Juntando tudo

Nós criamos todos os elementos necessários. Tudo que necessitamos é usar o nosso código e aproveitar.

1
interface eMailBody {
2
public function loadBody();
3
}
4
5
class eMail implements eMailBody {
6
	public function loadBody() {
7
		echo "This is Main Email body.<br />";
8
	} 
9
}
10
11
abstract class emailBodyDecorator implements eMailBody {
12
	
13
	protected $emailBody;
14
	
15
	public function __construct(eMailBody $emailBody) {
16
		$this->emailBody = $emailBody;
17
	}
18
	
19
	abstract public function loadBody();
20
	
21
} 
22
23
class christmasEmailBody extends emailBodyDecorator {
24
	
25
	public function loadBody() {
26
		
27
		echo 'This is Extra Content for Christmas<br />';
28
		$this->emailBody->loadBody();
29
		
30
	}
31
	
32
}
33
34
class newYearEmailBody extends emailBodyDecorator {
35
36
	public function loadBody() {
37
		
38
		echo 'This is Extra Content for New Year.<br />';
39
		$this->emailBody->loadBody();
40
		
41
	}
42
43
}

Agora iremos usar a classe decorator de várias formas, conforme necessário:

1
/*

2
 *  Normal Email

3
 */
4
5
$email = new eMail();
6
$email->loadBody();
7
8
// Output

9
This is Main Email body.
10
11
12
/*

13
 *  Email with Xmas Greetings

14
 */
15
16
$email = new eMail();
17
$email = new christmasEmailBody($email);
18
$email->loadBody();
19
20
// Output

21
This is Extra Content for Christmas
22
This is Main Email body.
23
24
/*

25
 *  Email with New Year Greetings

26
 */
27
28
$email = new eMail();
29
$email = new newYearEmailBody($email);
30
$email->loadBody();
31
32
33
// Output

34
This is Extra Content for New Year.
35
This is Main Email body.
36
37
/*

38
 *  Email with Xmas and New Year Greetings

39
 */
40
41
$email = new eMail();
42
$email = new christmasEmailBody($email);
43
$email = new newYearEmailBody($email);
44
$email->loadBody();
45
46
// Output

47
This is Extra Content for New Year.
48
This is Extra Content for Christmas
49
This is Main Email body.

Agora podemos ver que alteramos o corpo do email sem modificar a classe principal do email.

Conclusão

Toda a aplicação tem necessidades de algum tipo de alteração e/ou melhoramento em intervalos uniformes. Assim, em tal caso nós podemos implementar o padrão de design decorator e irá melhorar a qualidade do código e fazer o nosso código mais extensível.

Este foi o meu esforço para explicar-vos o padrão decorator, mas se tendes quaisquer comentários ou questões, por favor não hesitem e adiciona-os no feed abaixo.

Seja o primeiro a saber sobre novas traduções–siga @tutsplus_pt no Twitter!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.