C# - Padrões Estruturais Gof - Decorator
Neste artigo vou apresentar o padrão estrutural Gof Decorator. |
O padrão de projeto Decorator atribui responsabilidades adicionais a um objeto de forma dinâmica sem afetar o comportamento de outros objetos da mesma classe.
Este padrão fornece uma alternativa flexível à herança e permite estender (decorar) de forma dinâmica as características (propriedades e comportamentos) de uma classe qualquer.
O Decorator surgiu da necessidade de adicionar um comportamento, funcionalidade ou estado extra a um objeto em tempo de execução, quando o uso da Herança não for concebível, por ser um caso que geraria um número muito alto de classes derivadas.
Ele é mais eficiente do que criar várias subclasses, porque o comportamento de um objeto pode ser estendido sem você ter que definir um objeto inteiramente novo.
Dessa forma uma
característica do padrão Decorator e favorecer a
composição ou agregação sobre a herança.
Os problemas da herança.
A primeira opção que nos vem a mente quando precisamos alterar um comportamento de um objeto é usar herança e estender a classe. Na herança, o próprio objeto herda o comportamento da sua superclasse e um dos benefícios da herança é que ela captura o que é comum e o isola daquilo que é diferente.
Acontece que a herança apresenta os seguintes problemas:
-
A Herança é estática – Não podemos alterar o
comportamento de um objeto existente durante o tempo de execução só podemos
substituir todo o objeto por outro que foi criado de uma subclasse diferente.
- Viola o encapsulamento, visto que a mudança em
uma superclasse tem o potencial de afetar todas as subclasses;
- Causa um Forte Acoplamento entre as classes;
- As subclasses somente podem ter uma classe Pai (herança simples).
Lembrando que a linguagem C# que não suporta a herança múltipla;
Na figura temos a representação UML da herança :
Na composição ou agregação um objeto tem uma
referência com outro objeto e delega a ele alguma funcionalidade.
A grande vantagem da Composição/Agregação é que o comportamento pode ser
escolhido em tempo de execução em vez de estar amarrado em tempo de compilação.
A composição
apresenta uma menor dependência de implementações e assim cada classe esta
focada em apenas uma tarefa,
respeitando assim o principio da responsabilidade única.(SRP)
A seguir temos a representação UML para agregação e para composição :
a- Agregação
b-) Composição
Dessa forma na composição/agregação um objeto pode usar o comportamento de várias
classes, ter referências a múltiplos objetos, e delegar qualquer tipo de
trabalho a eles. e pode fazer isso em tempo de execução.
Exemplo de aplicação do padrão Decorator
Quando você acessa o site de um fabricante de automóveis para montar um veículo, você geralmente tem a opção de ir acrescentando acessórios a um veículo base ou padrão :
E você tem disponíveis diversos acessórios que serão usados conforme a
necessidade de cada cliente.
E cada cliente pode incluir os acessórios que achar conveniente ao veiculo base.
Assim um cliente pode montar um carro com câmbio manual outro com câmbio CVT; um cliente pode montar um carro com a cor prata outro com a cor branca; um cliente pode incluir ar condicionado digital outro ar condicionado manual :
Aqui o objeto veiculo pode receber diversos novos comportamentos/propriedades estendendo sua funcionalidade base.
Podemos replicar este cenário para diversos tipos de objetos como bebidas,
pizzas, vestuário, computadores,imóveis, etc.
Nestes cenários temos um exemplo onde o padrão Decorator pode ser usado para
incluir novas propriedades e comportamentos a um objeto existente estendendo
suas funcionalidades.
Diagrama UML
O diagrama UML do padrão Decorator segundo o Gof apresenta os seguintes participantes
1- Component - Define a interface para objetos que
podem ter responsabilidades adicionadas a eles dinamicamente;
2- ConcreteComponent - Implementa Component e sua
instância pode ser decorada pela inclusão de comportamento;
3- Decorator - Mantém uma referência para Component
e define uma interface compatível com Component;
4- ConcreteDecorator - Estende Decorator e pode
incluir ou sobrescrever uma funcionalidade;
Quando podemos usar o padrão Decorator
Podemos usar o padrão Decorator :
- Quando houver necessidade de anexar ou remover o comportamento de apenas
algumas instâncias de uma classe, em vez de todas as instâncias da classe.
- Quando a estensão através de herança é impraticável (explosão de classes);
- Quando temos uma classe que não pode ser herdada por estarmos herdando de uma
outra classe qualquer
- Queremos adicionar responsabilidades a objetos individuais de forma dinâmica e
transparente, sem afetar outros objetos;
- Quando não podemos usar herança (classe sealed)
Vantagens do padrão Decorator
Como vantagens do padrão Decorator temos que :
- É mais flexível que a herança pois adiciona responsabilidade em tempo de
execução e não em tempo de compilação
- Podemos ter qualquer número de decoradores e em qualquer ordem
- Estende a funcionalidade do objeto sem afetar outros objetos
De forma geral o padrão do decorador suporta o princípio de que as classes devem
ser abertas para extensão, mas fechadas para modificação.
Desvantagem
A principal desvantagem do padrão decorador é a manutenção do código, porque
esse padrão pode criar muitos decoradores semelhantes que às vezes são difíceis
de manter e distinguir.
Assim podemos ter um aumento na complexidade do código e um grande número de
objetos criados.
Aplicando o padrão Decorator
Veremos agora um exemplo prático de aplicação do padrão Composite.
Neste exemplo
vamos decorar um objeto pizza com opcionais como : Massa
Especial, Bacon e Borda Recheada.
Inicialmente teremos o objeto padrão Pizza que tem um preço e a definição de
opcionais :
A seguir iremos decorar este objeto incluindo o opcional
Massa Especial e calculando o novo preço; Depois vamos decorar este
objeto incluindo o opcional Bacon e recalculando o
preço e a seguir vamos decorar este objeto incluindo o opcional
Borda Recheada e recalculado o preço:
Aqui vamos aplicar o padrão Decorator onde estamos
incluindo novas funcionalidades a um objeto existente usando a composição.
Note que podemos visualizar os objetos decorators como invólucros ou
wrappers.
Para realizar a implementação vamos criar uma aplicação Console no ambiente do .NET 5.0 usando o VS 2019. A seguir temos o diagrama de classes que foi gerado pela implementação feita no VS 2019:
1- A interface IPizza : Representa o
Component que define a interface que podemos
adicionar a outros objetos;
2- A classe
Pizza : É o
ConcreteComponent e Implementa o Component e define o preço e o valor
padrão do opcional
e sua instância pode ser decorada pela inclusão de opcionais que são os novos
comportamentos;
3- A classe abstrata PizzaDecorator :
Representa o Decorator e mantém uma referência a Component definindo um
variável do tipo IPizza e injetando uma instância
do tipo IPizza no construtor da classe e a seguir sobrescreve os métodos Preco() e
Opcionais() usando a instância de IPizza;
4- As classes BaconDecorator, BordaRecheadaDecorator e MassaEspecialDecorator representam o ConcreteDecorator e estendem PizzaDecorator sobrescrevendo as funcionalidades que no nosso exemplo são o Preco e Opcionais;
A seguir temos o código usado na implementação:
1- Interface IPizza (Component)
public interface IPizza { string Opcionais(); decimal Preco(); } |
2- Classe Pizza (ConcreteComponent)
public class Pizza : IPizza { public string Nome { get; set; } public Pizza(string nome) { Nome = nome; } public string Opcionais() { var opcional = $"Pizza de {Nome} "; return opcional; } public decimal Preco() |
3- Classe PizzaDecorator (Decorator)
public abstract class PizzaDecorator : IPizza
{
protected readonly IPizza _pizza;
public PizzaDecorator(IPizza pizza)
{
_pizza = pizza;
}
public virtual string Opcionais()
{
var opcional = _pizza.Opcionais();
return opcional;
}
public virtual decimal Preco()
{
var preco = _pizza.Preco();
return preco;
}
}
|
5- MassaEspecialDecorator
|
6- BaconDecorator
|
7- BordaRecheadaDecorator
|
8- Program
Na classe Program inicialmente criamos uma instancia de Pizza que será o objeto que desejamos decorar. A seguir vamos aplicar o padrão Decorator criando uma nova instancia de Pizza e incluindo os opcionais: - massa especial, bacon e borda recheada no objeto Pizza e recalculando o preço total :
static void Main(string[] args) { IPizza pizzaMussarela = new Pizza("Mussarela"); Console.WriteLine(pizzaMussarela.Opcionais()); Console.WriteLine($"Preço R$ {pizzaMussarela.Preco()}\n");
Console.WriteLine("Tecle algo para aplicar o padrão Decorator"); IPizza massaEspecial = new MassaEspecialDecorator(pizzaMussarela); //exibe o preco e o tipo Console.ReadKey(); |
A execução do projeto irá apresentar o seguinte resultado:
Observe que o
padrão Decorador é parecido com o padrão
Composite pois ambos usam o
princípio de recursividade.
Assim, o Decorator pode ser visto como uma versão simplificada do padrão
Composite,
porém a diferença é que o Decorator
apenas adiciona responsabilidades e não é usado para agregar objetos.
Pegue o código
do projeto aqui :
Decorator_Exemplo.zip
"Quanto ao mais,
irmãos, tudo o que é verdadeiro, tudo o que é honesto, tudo o que é justo,
tudo o que é puro, tudo o que é amável, tudo o que é de boa fama, se há
alguma virtude, e se há algum louvor, nisso pensai."
Filipenses 4:8
Referências:
NET - Unit of Work - Padrão Unidade de ...
NET - O padrão de projeto Decorator
NET - Padrão de Projeto Builder
C# - O Padrão Strategy (revisitado)
NET - O padrão de projeto Command
NET - Apresentando o padrão Repository