Menu
Coddy logo textTech

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.00

O 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.

challenge icon

Desafio

Fácil

Vamos 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 interface IPizza no namespace PizzaShop com dois métodos: GetDescription() retornando uma string e GetPrice() retornando um decimal. Em seguida, crie uma classe PlainPizza que implemente esta interface - ela deve retornar "Pizza" como sua descrição e 8.00m como seu preço base.
  • ToppingDecorators.cs: Crie uma classe abstrata ToppingDecorator que implemente IPizza e 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 adiciona 1.50m ao preço
    • PepperoniTopping - anexa ", Pepperoni" à descrição e adiciona 2.00m ao preço
    • MushroomTopping - anexa ", Mushrooms" à descrição e adiciona 1.25m ao 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.75

Observe como você pode adicionar a mesma cobertura várias vezes! Se a entrada for cheese,cheese, você obteria:

Pizza, Cheese, Cheese
11

Isso 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.00

Use 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
    }
}
quiz iconTeste seus conhecimentos

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