Menu
Coddy logo textTech

Padrão Decorator

Parte da seção Programação Orientada a Objetos do Journey de Dart da Coddy — lição 96 de 110.

O padrão Decorator permite adicionar novos comportamentos a objetos dinamicamente, envolvendo-os em objetos especiais chamados decoradores. Ao contrário da herança, que adiciona comportamento em tempo de compilação, os decoradores anexam responsabilidades adicionais em tempo de execução sem modificar a classe original.

A ideia principal é que tanto o objeto original quanto os decoradores implementam a mesma interface.

Um decorador envolve o objeto original, delega a ele e adiciona seu próprio comportamento antes ou depois:

abstract class Coffee {
  String getDescription();
  double getCost();
}

class SimpleCoffee implements Coffee {
  @override
  String getDescription() => 'Simple Coffee';

  @override
  double getCost() => 2.0;
}

class MilkDecorator implements Coffee {
  final Coffee _coffee;
  MilkDecorator(this._coffee);

  @override
  String getDescription() => '${_coffee.getDescription()}, Milk';

  @override
  double getCost() => _coffee.getCost() + 0.5;
}

class SugarDecorator implements Coffee {
  final Coffee _coffee;
  SugarDecorator(this._coffee);

  @override
  String getDescription() => '${_coffee.getDescription()}, Sugar';

  @override
  double getCost() => _coffee.getCost() + 0.2;
}

void main() {
  Coffee coffee = SimpleCoffee();
  coffee = MilkDecorator(coffee);
  coffee = SugarDecorator(coffee);

  print(coffee.getDescription());  // Simple Coffee, Milk, Sugar
  print(coffee.getCost());         // 2.7
}

Cada decorador envolve o objeto anterior e o aprimora. Você pode empilhar múltiplos decoradores em qualquer combinação, criando um comportamento flexível sem a necessidade de criar uma subclasse para cada combinação possível. Este padrão é ideal quando você precisa adicionar funcionalidades a objetos sem afetar outros objetos da mesma classe.

challenge icon

Desafio

Fácil

Vamos construir um sistema de pedidos de pizza usando o padrão Decorator! Você criará uma pizza base que pode ser aprimorada com várias coberturas, onde cada cobertura envolve o item anterior e adiciona tanto à descrição quanto ao preço.

Você organizará seu código em dois arquivos:

  • pizza.dart: Este arquivo contém sua interface de pizza e todas as classes decoradoras. Comece com uma classe abstrata Pizza que define dois métodos: String getDescription() e double getPrice(). Em seguida, crie uma classe PlainPizza que implementa Pizza - ela retorna Plain Pizza como sua descrição e custa 8.0. Agora, construa três classes decoradoras que envolvem uma Pizza e a aprimoram:
    • CheeseDecorator - adiciona , Cheese à descrição e 1.5 ao preço
    • PepperoniDecorator - adiciona , Pepperoni à descrição e 2.0 ao preço
    • MushroomDecorator - adiciona , Mushrooms à descrição e 1.25 ao preço
    Cada decorador deve implementar Pizza, aceitar uma Pizza em seu construtor e delegar para a pizza envolvida antes de adicionar sua própria contribuição.
  • main.dart: Importe seu arquivo de pizza e demonstre como os decoradores se empilham para construir um pedido personalizado. Comece com uma PlainPizza, depois envolva-a com CheeseDecorator, depois envolva isso com PepperoniDecorator e, finalmente, envolva tudo com MushroomDecorator. Imprima a descrição final da pizza e, em seguida, imprima Total: $ seguido pelo preço.

Observe como cada decorador se baseia no anterior - você está criando uma pizza completa empilhando decoradores simples de responsabilidade única!

Saída esperada:

Plain Pizza, Cheese, Pepperoni, Mushrooms
Total: $12.75

Folha de consulta

O padrão Decorator adiciona novos comportamentos a objetos dinamicamente, envolvendo-os em objetos decoradores. Tanto o objeto original quanto os decoradores implementam a mesma interface.

Componentes principais:

  • Uma classe abstrata ou interface que define os métodos comuns
  • Uma classe concreta que implementa a funcionalidade base
  • Classes decoradoras que envolvem o objeto original e o aprimoram

Cada decorador:

  • Implementa a mesma interface que o objeto que ele decora
  • Mantém uma referência ao objeto envolvido
  • Delega chamadas para o objeto envolvido
  • Adiciona seu próprio comportamento antes ou depois da delegação
abstract class Coffee {
  String getDescription();
  double getCost();
}

class SimpleCoffee implements Coffee {
  @override
  String getDescription() => 'Simple Coffee';

  @override
  double getCost() => 2.0;
}

class MilkDecorator implements Coffee {
  final Coffee _coffee;
  MilkDecorator(this._coffee);

  @override
  String getDescription() => '${_coffee.getDescription()}, Milk';

  @override
  double getCost() => _coffee.getCost() + 0.5;
}

// Empilhar múltiplos decoradores
Coffee coffee = SimpleCoffee();
coffee = MilkDecorator(coffee);
coffee = SugarDecorator(coffee);

Os decoradores podem ser empilhados em qualquer combinação, criando um comportamento flexível sem a necessidade de criar subclasses para cada combinação possível.

Experimente você mesmo

import 'pizza.dart';

void main() {
  // TODO: Criar uma PlainPizza
  
  // TODO: Envolvê-la com CheeseDecorator
  
  // TODO: Envolver isso com PepperoniDecorator
  
  // TODO: Envolver tudo com MushroomDecorator
  
  // TODO: Imprimir a descrição final da pizza
  
  // TODO: Imprimir "Total: $" seguido pelo 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