Padrão Decorator
Parte da seção Programação Orientada a Objetos do Journey de C# da Coddy. Lição 58 de 70.
O padrão Decorator é um padrão estrutural que permite anexar novos comportamentos a objetos envolvendo-os em objetos especiais chamados decoradores. Diferentemente da herança, que adiciona comportamento em tempo de compilação, os decoradores adicionam funcionalidades dinamicamente em tempo de execução, sem modificar a classe original.
O padrão funciona fazendo com que os decoradores implementem a mesma interface que o objeto que envolvem. Cada decorador mantém uma referência ao objeto envolvido e delega as chamadas a ele, adicionando seu próprio comportamento antes ou depois:
public interface ICoffee
{
string GetDescription();
decimal GetCost();
}
public class SimpleCoffee : ICoffee
{
public string GetDescription() => "Coffee";
public decimal GetCost() => 2.00m;
}
public abstract class CoffeeDecorator : ICoffee
{
protected ICoffee _coffee;
public CoffeeDecorator(ICoffee coffee) => _coffee = coffee;
public virtual string GetDescription() => _coffee.GetDescription();
public virtual decimal GetCost() => _coffee.GetCost();
}
public class MilkDecorator : CoffeeDecorator
{
public MilkDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription() => _coffee.GetDescription() + ", Milk";
public override decimal GetCost() => _coffee.GetCost() + 0.50m;
}O poder dos decoradores está na capacidade de serem empilhados. Você pode envolver um objeto decorado com outro decorador:
ICoffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new MilkDecorator(coffee); // Leite duplo!
Console.WriteLine(coffee.GetDescription()); // Café, Leite, Leite
Console.WriteLine(coffee.GetCost()); // 3.00O padrão Decorator é ideal quando você precisa adicionar responsabilidades a objetos sem criar uma explosão de subclasses. Cada decorador se concentra em uma única responsabilidade, e você os combina conforme necessário para construir exatamente o comportamento desejado.
Desafio
FácilVamos criar um sistema de pedidos de pizza usando o padrão Decorator. Você criará uma pizza base e, em seguida, adicionará coberturas dinamicamente, com cada cobertura envolvendo a camada anterior e adicionando algo tanto à descrição quanto ao preço.
Você organizará seu código em três arquivos:
Pizza.cs: Defina uma interfaceIPizzano namespacePizzaShopcom dois métodos:GetDescription(), que retorna uma string, eGetPrice(), que retorna um decimal. Em seguida, crie uma classePlainPizzaque implemente essa interface — ela deve retornar"Pizza"como descrição e8.00mcomo preço base.ToppingDecorators.cs: Crie uma classe abstrataToppingDecoratorque implementeIPizzae mantenha uma referência à pizza envolvida (passada pelo construtor). Seus métodos virtuais devem delegar para a pizza envolvida. Em seguida, crie três decorators concretos:CheeseTopping— acrescenta", Cheese"à descrição e adiciona1.50mao preçoPepperoniTopping— acrescenta", Pepperoni"à descrição e adiciona2.00mao preçoMushroomTopping— acrescenta", Mushrooms"à descrição e adiciona1.25mao preço
Program.cs: Reúna tudo começando com uma pizza simples e envolvendo-a com coberturas com base na entrada. Cada decorator de cobertura envolve a pizza anterior, construindo o pedido final camada por camada.
Você receberá uma entrada:
- Uma lista de coberturas separadas por vírgulas para adicionar (por exemplo,
cheese,pepperoni,cheese)
Comece com uma PlainPizza e, em seguida, envolva-a com o decorator apropriado para cada cobertura, na ordem em que aparecem. As coberturas válidas são cheese, pepperoni e mushrooms. Depois de aplicar todas as coberturas, imprima a descrição em uma linha e o preço total na linha seguinte.
Por exemplo, se a entrada for cheese,pepperoni,mushrooms, a saída deverá ser:
Pizza, Cheese, Pepperoni, Mushrooms
12.75Observe como você pode adicionar a mesma cobertura várias vezes! Se a entrada for cheese,cheese, você obterá:
Pizza, Cheese, Cheese
11.00Isso demonstra a flexibilidade do padrão decorator — cada decorator envolve qualquer pizza que recebe, seja uma pizza simples ou uma que já tenha decorators, permitindo empilhar comportamentos dinamicamente!
Experimente você mesmo
using System;
using PizzaShop;
class Program
{
public static void Main(string[] args)
{
// Lê a lista de coberturas separada por vírgulas
string input = Console.ReadLine();
string[] toppings = input.Split(',');
// TODO: Comece com uma PlainPizza
// TODO: Percorra cada cobertura e envolva a pizza
// com o decorador apropriado baseado no nome da cobertura:
// - "cheese" -> CheeseTopping
// - "pepperoni" -> PepperoniTopping
// - "mushrooms" -> MushroomTopping
// TODO: Imprima a descrição final e o preço
}
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de POO
Arquivos ExternosNamespaces e DiretivasIntrodução a Classes e ObjetosA Palavra-chave 'this'Métodos e ParâmetrosCampos vs PropriedadesConstrutoresInicializadores de ObjetosRecapitulação - Calculadora Simples4Herança
Sintaxe Básica de Herança (:)A Palavra-chave 'base'Palavras-chave Virtual e OverrideClasses SeladasA Classe Base 'object'Recapitulação - Hierarquia de Funcionários7Recursos Avançados
Sobrecarga de OperadoresIndexadores (this[])Sobrescrita de ToString()Métodos de ExtensãoRecapitulação - Lista Personalizada10Padrões de Projeto Parte 1
Introdução aos Padrões de ProjetoSingleton Thread-SafePadrão FactoryPadrão Observer (Eventos)Padrão Strategy2Propriedades e Membros Estáticos
Propriedades AutoimplementadasPropriedades de Apenas Leitura/EscritaCampos e Métodos EstáticosClasses EstáticasMembros com Corpo de Expressão5Polimorfismo e Interfaces
Polimorfismo: Compilação vs ExecuçãoInterface vs Classe AbstrataMúltiplas InterfacesInterfaces ExplícitasUpcasting e DowncastingRecapitulação - Calculadora de Formas8Conceitos Avançados de POO
Composição sobre HerançaGenerics (Classes e Métodos)Delegates e EventosAtributos e ReflectionIDisposable e a Instrução usingFundamentos de Injeção de Dependência11Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão Composite3Arquitetura de Classes
Dados de Instância vs EstáticosPalavras-chave 'readonly' e 'const'Campos de SuporteRecapitulação - Gerenciador de Conta Bancária6Encapsulamento
Modificadores de AcessoPropriedades para EncapsulamentoImplementação de Ocultação de DadosPadrões de ImutabilidadeRecapitulação - Registros de Estudantes9Argumentos Variáveis
A Palavra-chave 'params'Parâmetros OpcionaisArgumentos NomeadosSobrecarga de MétodosPratique por conta própria: Compilador de C# online