O Decorator é um dos design patterns mais populares e úteis na programação orientada a objetos e é uma alternativa flexível e poderosa às subclasses tradicionais. Porém, sua implementação é relativamente semelhante ao Composite, o que pode gerar algumas dúvidas.
Hoje, vamos descomplicar a aplicabilidade e conceito do padrão de projeto Decorator, abordado pela GoF (Gang of Four) em seu livro “Padrões de Projeto”.
Continue lendo e descubra o que é o Decorator, quando usar e veja um exemplo prático!
Qual é a definição do padrão de projeto Decorator?
O Decorator é um padrão de projeto estrutural que permite adicionar comportamentos a um objeto dinamicamente, sem precisar alterar o código original do objeto.
Ou seja, ele permite que você adicione ou remova comportamentos a um objeto de forma flexível e modular, sem a necessidade de alterar sua estrutura interna ou criar subclasses para cada combinação possível de comportamentos.
O Decorator, também chamado de “Wrapper”, é o quinto padrão de projeto estrutural listado pela GoF.
Sua intenção é relativamente simples de se entender, uma vez que ela apenas busca acrescentar alguma funcionalidade dentro de classes.
Conforme descrito pelos autores: “Sua intenção é, dinamicamente, agregar responsabilidades adicionais a um objeto. Os Decorators fornecem uma alternativa flexível ao uso de subclasses para extensão de funcionalidades”.
Aqui na Tech Writers também temos um artigo abordando outro padrão de projeto, o Strategy, que você pode conferir também.
Diagrama oficial do livro “Padrões de Projetos: Soluções Reutilizáveis de Software Orientados a Objetos”
Quando usar o Decorator?
O padrão de projeto Decorator pode ser útil quando você precisa adicionar funcionalidades extras a um objeto sem alterar sua estrutura original, servindo de alternativa às subclasses tradicionais.
Ele também é útil quando se tem várias opções de personalização ou combinação de funcionalidades e você quer evitar a criação de subclasses excessivas.Ou seja, ele é recomendado sempre que você precisar adicionar responsabilidades a um objeto de maneira flexível e dinâmica.
Exemplo prático do uso do padrão Decorator
Para entender melhor o conceito, vamos analisar um exemplo prático da implementação do padrão Decorator. Vejamos o seguinte cenário:
Em uma determinada lanchonete, cada um dos lanches disponíveis no cardápio possui diferentes versões: normal, gourmet e vegano.
Trazendo para o mundo do desenvolvimento, de qual forma poderíamos reaproveitar o máximo de métodos/atributos em comum entre cada um dos lanches (objetos)?
Aqui, podemos aplicar o Decorator, onde o lanche gourmet e vegano são “variantes” de um lanche comum.
Segue exemplo abaixo, utilizando um X-Salada e um Cachorro-quente como opções de cardápio:
Podemos começar com uma declaração simples do que poderia ser o lanche. Para simplificar o Decorator, vamos apenas alterar o preço entre gourmet, vegano e comum. Assim, vamos ficar apenas com o método getValor().
Aqui, temos a implementação dos nossos lanches “comuns”. Estes objetos também são chamados de “componentes concretos”, dentro deste padrão de projeto.
Cada um dos componentes concretos possui seus próprios atributos e métodos. Para simplificar, vamos focar apenas no valor dos lanches.
Então, a primeira implementação do nosso Decorator aparece. Neste primeiro momento, ele serve apenas como uma classe intermediária para os decoradores concretos, que virão a seguir.
Em alguns cenários, inclusive no nosso, a criação deste método é opcional, uma vez que ele apenas define a forma de instanciação dos decoradores filhos.
Ainda assim, neste exemplo nós seguiremos a estrutura proposta pela GoF. Uma das principais desvantagens desta classe intermediária é o aumento de complexidade no código.
Em um cenário mais real, esta classe poderia possuir alguns métodos e obrigar a sua implementação para as classes filhas.
No nosso caso, estamos apenas delegando a implementação de getValor(), que vem da interface Lanche.
Como decoradores concretos, conforme nosso exemplo, teríamos os seguintes:
Como podemos observar, os Decorators, especificamente no nosso caso, possuem apenas uma modificação de incremento de valor.
Este “comportamento extra” trazido aos lanches, servirá como modelo para decorar componentes concretos, como por exemplo o XSalada ou o CachorroQuente.
Quando um LancheGourmet for instanciado com um XSalada como parâmetro portanto, um valor de “9,95” será acrescentado ao valor original do x-salada, e quando um LancheVegano for instanciado com um CachorroQuente como parâmetro portanto, um valor de “7,5” será acrescentado ao valor original do cachorro-quente.
Obviamente, o incremento de valor é apenas um exemplo. Utilizando deste conceito, qualquer adição de funcionalidade ou alteração de comportamento poderia ser aplicado nos lanches.
O importante é compreender o objetivo: ao invés de criarmos as classes CachorroQuente, CachorroQuenteVegano, CachorroQuenteGourmet, XSalada, XSaladaVegano, XSaladaGourmet etc., basta decorarmos os componentes concretos (XSalada e CachorroQuente) com nossos decoradores concretos.
E você, já conhecia este design pattern? Ainda tem alguma dúvida sobre o Padrão Decorator? Deixe um comentário e confira outros conteúdos Tech Writers!