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 ao envolvê-los em objetos especiais chamados decoradores. Ao contrário da herança, que adiciona comportamento em tempo de compilação, os decoradores adicionam funcionalidade 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 eles envolvem. Cada decorador mantém uma referência ao objeto envolvido e delega 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 reside na sua capacidade de empilhamento. 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 decorator foca em uma única preocupação, e você os combina conforme necessário para construir o comportamento exato que deseja.
Desafio
FácilVamos construir um sistema de pedidos de pizza usando o padrão Decorator. Você criará uma pizza base e, em seguida, adicionará coberturas dinamicamente a ela, com cada cobertura envolvendo a camada anterior e adicionando 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()retornando uma string eGetPrice()retornando um decimal. Em seguida, crie uma classePlainPizzaque implemente esta interface - ela deve retornar"Pizza"como sua descrição e8.00mcomo seu preço base.ToppingDecorators.cs: Crie uma classe abstrataToppingDecoratorque implementeIPizzae mantenha uma referência para a pizza envolvida (passada através do construtor). Seus métodos virtuais devem delegar para a pizza envolvida. Em seguida, crie três decoradores concretos:CheeseTopping- anexa", Cheese"à descrição e adiciona1.50mao preçoPepperoniTopping- anexa", Pepperoni"à descrição e adiciona2.00mao preçoMushroomTopping- anexa", Mushrooms"à descrição e adiciona1.25mao preço
Program.cs: Reúna tudo começando com uma pizza simples e envolvendo-a com coberturas baseadas na entrada. Cada decorador de cobertura envolve a pizza anterior, construindo o pedido final camada por camada.
Você receberá uma entrada:
- Uma lista separada por vírgulas de coberturas para adicionar (ex:
cheese,pepperoni,cheese)
Comece com uma PlainPizza, então envolva-a com o decorador apropriado para cada cobertura na ordem em que aparecem. Coberturas válidas são cheese, pepperoni, e mushrooms. Após 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 deve ser:
Pizza, Cheese, Pepperoni, Mushrooms
12.75Observe como você pode adicionar a mesma cobertura várias vezes! Se a entrada for cheese,cheese, você obteria:
Pizza, Cheese, Cheese
11Isso demonstra a flexibilidade do padrão decorator - cada decorador envolve qualquer pizza que receba, seja uma pizza simples ou uma já decorada, permitindo que você empilhe comportamentos dinamicamente!
Folha de consulta
O padrão Decorator é um padrão estrutural que anexa novos comportamentos a objetos ao envolvê-los em objetos decoradores especiais. Ele adiciona funcionalidade dinamicamente em tempo de execução sem modificar a classe original.
Os decoradores implementam a mesma interface que o objeto que eles envolvem, mantêm uma referência ao objeto envolvido e delegam chamadas a ele enquanto adicionam seu próprio comportamento:
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;
}Os decoradores podem ser empilhados ao 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()); // Coffee, Milk, Milk
Console.WriteLine(coffee.GetCost()); // 3.00Use o padrão Decorator quando precisar adicionar responsabilidades a objetos sem criar muitas subclasses. Cada decorador foca em uma única preocupação e pode ser combinado conforme necessário.
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étodos