Padrões de projeto podem ser vistos como uma solução que já foi testada para um problema, dessa forma reutilizamos a experiência de outros desenvolvedores que tiveram problemas semelhantes. Portanto, um padrão de projeto geralmente descreve uma solução ou uma instância da solução que foi utilizada para resolver um problema específico. Padrões de projetos são soluções para problemas que alguém um dia teve e resolveu aplicando um modelo que foi documentado e que você pode adaptar integralmente ou de acordo com a necessidade da sua solução.

A ideia original dos Padrões de Projetos surgiu com Christopher Alexander quando ele propôs a criação de catálogos de padrões para arquitetura. O próprio Christopher Alexander definiu os Padrões dessa forma: "Um padrão descreve um problema que ocorre inúmeras vezes em determinado contexto, e descreve ainda a solução para esse problema, de modo que essa solução possa ser utilizada sistematicamente em distintas situações".

O grande objetivo dos padrões de projetos é ajudar os desenvolvedores a estruturar os seus aplicativos de maneiras mais flexíveis, fáceis de entender e manter.

As principais propriedades dos padrões de projetos são:

  • Capturar o conhecimento e a experiência de especialistas em projeto de software.
  • Especificar abstrações que estão acima do nível de classes ou objetos isolados ou de componentes.
  • Definir um vocabulário comum para a discussão de problemas e soluções de projeto.
  • Facilitar a documentação e manutenção da arquitetura do software.
  • Auxiliar o projeto de uma arquitetura com determinadas propriedades.
  • Auxiliar o projeto de arquiteturas mais complexas.

Abaixo será descrito um padrão de projeto bastante importante e muito utilizado também pela API Java, o padrão Decorator. O padrão Decorator é utilizado quando precisa-se anexar responsabilidades dinamicamente sem precisar de uma grande hierarquia de subclasses.

Funcionamento

A descrição original do Padrão Decorator é: "O Padrão Decorator anexa responsabilidades adicionais a um objeto dinamicamente. Os decoradores fornecem uma alternativa flexível de subclasse para estender a funcionalidade".

O Padrão Decorator tem como característica o seguinte:

  • Os decoradores têm o mesmo supertipo que os objetos que eles decoram;
  • Você pode usar um ou mais decoradores para englobar um objeto;
  • Uma vez que o decorador tem o mesmo supertipo que o objeto decorado, podemos passar um objeto decorado no lugar do objeto original (englobado);
  • O decorador adiciona seu próprio comportamento antes e/ou depois de delegar o objeto que ele decora o resto do trabalho;
  • Os objetos podem ser decorados a qualquer momento, então podemos decorar os objetos de maneira dinâmica no tempo de execução com quantos decoradores desejarmos.

O Diagrama de classe da Figura 1 mostra mais detalhes sobre o funcionamento do padrão Decorator.

Diagrama de classe do Padrão Decorator
Figura 1. Diagrama de classe do Padrão Decorator

Nesse diagrama de classes nota-se a presença da interface Component que é implementada pela classe concreta ConcreteComponent, que é o objeto no qual vamos adicionar dinamicamente um novo comportamento. As classes Decorator implementam a mesma interface abstrata que o componente que decorarão. Já as classes ConcreteDecorator possuem uma variável de instância para a classe que será decorada. Os ConcreteDecorator também podem adicionar mais comportamentos (método addedBehavior() no diagrama ConcreteDecoratorB) ou mais atributos (addedState no diagrama ConcreteDecoratorA). Portanto a ideia basicamente é que através dos Decorators possamos adicionar comportamentos aos componentes bases. Todo o funcionamento das peças que compõem o diagrama serão mais facilmente entendidos quando analisarmos os exemplos.

O Decorator é mais utilizado quando quisermos adicionar responsabilidades a objetos dinamicamente, e quando a extensão por subclasses é impraticável, pois teríamos muitas alterações e dessa forma diversas subclasses.

Exemplo de Implementação

Segue na Listagem 1 um exemplo de implementação em Java utilizando o Padrão Decorator. Inicialmente define-se abaixo uma Janela abstrata e a implementação dessa Janela abstrata.


abstract class Janela {

	public abstract void draw();

}

class JanelaSimples extends Janela {
	
	public void draw() {
		System.out.println(“desenha uma janela”);
	}
}

abstract class JanelaDecorator extends Janela {
	
	protected Janela janelaDecorada;

	public JanelaDecorator(Janela janelaDecorada) {
		this.janelaDecorada = janelaDecorada;
	}

}
Listagem 1. Exemplo da primeira parte de implementação em Java do padrão Decorator

Veja que temos a implementação da primeira parte do padrão Decorator. Nesse exemplo criou-se uma janela simples onde queremos adicionar mais coisas como barras de rolagem, caixas de texto, labels, etc. Assim, criou-se também a classe JanelaDecorador que será estendida pelos nossos decoradores que irão inserir propriedades na nossa janela.

Na Listagem 2 segue a implementação dos decoradores.


class DecoradorBarraVertical extends JanelaDecorator {

	public DecoradorBarraVertical(Janela janelaDecorada) {
		super(janelaDecorada);
	}

	public void draw() {
		drawBarraVertical();
		janelaDecorada.draw();
	}

	private void drawBarraVertical() {
		System.out.println(“desenha uma barra vertical na janela”);
	}

}
Listagem 2. Exemplo de implementação dos decoradores em Java

No código temos um decorador que anexa uma barra vertical ao nosso componente principal (JanelaSimples). Portanto, anexamos responsabilidades ao nosso componente base. Se quiséssemos criar uma barra horizontal, menus, botões, faríamos outros decoradores para anexar mais responsabilidades.

Uma situação importante a se observar são as duas chamadas ao método draw(). A chamada a drawBarraVertical() chama ele próprio para pintar na tela a sua própria barra vertical e a outra chamada que veio da superclasse é a responsável por chamar outros decoradores que precisamos anexar na nossa classe.

Na Listgem 3 segue um exemplo de execução de uma janela simples com uma barra vertical.


public class DecoradorTeste {
	
	public static void main(String args[]) {
		Janela janelaDecorada = DecoradorBarraVertical(new JanelaSimples());
		janelaDecorada.draw();
	}
}
Listagem 3. Exemplo de implementação dos decoradores em Java

Note que a execução do janelaDecorada.draw() chamará o método draw da janela simples combinado com o draw() do nosso decorator. E se quiséssemos adicionar agora uma barra horizontal? Teríamos algo como:


Janela janelaDecorada = new DecoradorBarraHorizontal(new 
    DecoradorBarraVertical(new JanelaSimples()));

Assim tem-se um decorator anexando outras responsabilidades e formando uma cadeia de objetos com seus comportamentos e métodos específicos.

Uso do Padrão no Java

O Java também utiliza bastante o padrão de projeto Decorator. A API java.io é amplamente baseada nesse padrão de projeto.

Os objetos da API java.io que tipicamente usam esse objeto são os bem conhecidos LineNumberInputStream, BufferedInputStream e FileInputStream. Na Figura 2 veja como eles estão decorados.

Organização típica do padrão decorator para os objetos acima
Figura 2. Organização típica do padrão decorator para os objetos acima

Temos que LineNumberInputStream é um decorador concreto que tem como função contar as linhas de um determinado arquivo. O BufferInputStream também é um decorador concreto que tem como objetivo colocar a entrada em buffer para melhorar o desempenho e também oferece o método readLine() que lê a entrada baseada em caracteres linha por linha. Por fim, FileInputStream é quem está sendo decorado (componente concreto no diagrama de classe) e oferece um componente básico através do qual os bytes serão lidos.

Conclusão

Como pode-se notar neste artigo, o padrão Decorator usa a herança apenas para ter uma correspondência de tipo e não para obter o comportamento. Assim, quando compõe-se um decorador com um componente, adiciona-se um novo comportamento, nota-se que estamos adquirindo um novo comportamento e não herdando-o de alguma superclasse. Isso nos dá muito mais flexibilidade para compor mais objetos sem alterar uma linha de código, tudo em tempo de execução e não em tempo de compilação como ocorre com a herança. Todos esses benefícios nos são disponibilizados pelo uso do padrão Decorator. Uma desvantagem do padrão é que teremos inúmeras classes pequenas que pode ser bastante complicado para um desenvolvedor que está tentando entender o funcionamento da aplicação. Assim, precisamos avaliar esses casos e optar por uma solução que de repente não seja usando decoradores.

Bibliografia