Padrão Decorator
Parte da seção Programação Orientada a Objetos do Journey de Java da Coddy — lição 72 de 87.
O Decorator Pattern é um padrão de projeto estrutural que permite acoplar 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.
Imagine uma cafeteria onde você começa com um café básico e pode adicionar extras como leite, açúcar ou chantilly. Cada adição envolve o café original, adicionando seu custo e descrição. O padrão usa uma interface comum para que os objetos decorados possam ser tratados da mesma forma que o original:
interface Coffee {
String getDescription();
double getCost();
}
class SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double getCost() {
return 2.0;
}
}
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public double getCost() {
return coffee.getCost() + 0.5;
}
}O decorador mantém uma referência ao objeto envolvido e delega chamadas a ele, enquanto adiciona seu próprio comportamento. Você pode empilhar múltiplos decoradores para combinar funcionalidades:
Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);
System.out.println(order.getDescription()); // Café Simples, Leite, Açúcar
System.out.println(order.getCost()); // 2.75O Padrão Decorator brilha quando você precisa adicionar responsabilidades a objetos sem criar uma explosão de subclasses. Cada decorador é independente, então você pode misturá-los e combiná-los livremente para criar exatamente a combinação de comportamentos que você precisa.
Desafio
FácilVamos construir um sistema de notificação usando o Decorator Pattern! Você criará uma notificação base que pode ser aprimorada com diferentes canais de entrega—alertas por e-mail, alertas por SMS e notificações push—envolvendo decoradores uns nos outros. Isso reflete sistemas do mundo real onde uma única mensagem pode precisar ser enviada através de múltiplos canais simultaneamente.
Você organizará seu código em quatro arquivos:
Notifier.java: Defina a interfaceNotifierque serve como a interface do componente para o seu padrão decorator. Ela deve declarar um métodosend(String message)que lida com o envio de notificações.BasicNotifier.java: Crie a classe de componente concreto que implementaNotifier. Isso representa a forma mais simples de notificação—um alerta in-app. Quandosendfor chamado, ele deve imprimirIn-App: [message].NotifierDecorators.java: Construa sua hierarquia de decoradores aqui. Comece com uma classe abstrataNotifierDecoratorque implementaNotifiere mantém uma referência a um objetoNotifierenvolvido (passado através de seu construtor).Em seguida, crie três decoradores concretos que estendem
NotifierDecorator:EmailDecorator- Quandosendfor chamado, ele primeiro delega para o notifier envolvido, depois imprimeEmail: [message].SMSDecorator- Quandosendfor chamado, ele primeiro delega para o notifier envolvido, depois imprimeSMS: [message].PushDecorator- Quandosendfor chamado, ele primeiro delega para o notifier envolvido, depois imprimePush: [message].Main.java: Reúna seu sistema de decoradores! Você receberá duas entradas: a mensagem de notificação (String) e uma lista de canais separada por vírgulas para adicionar (String), por exemplo"email,sms"ou"push,email,sms".Comece com um
BasicNotifier. Então, com base na entrada de canais, envolva-o com os decoradores apropriados na ordem em que aparecem. Os nomes de canais válidos sãoemail,smsepush.Finalmente, chame
sendcom sua mensagem no notifier totalmente decorado.
Você receberá duas entradas em ordem: a mensagem a ser enviada (String) e os canais a serem ativados (String, separados por vírgula).
Por exemplo, com as entradas Server is down! e email,push, sua saída seria:
In-App: Server is down!
Email: Server is down!
Push: Server is down!Observe como cada decorador adiciona seu próprio comportamento enquanto preserva a notificação original. A ordem de envolvimento determina a ordem da saída—o componente mais interno (BasicNotifier) executa primeiro, depois cada decorador adiciona sua camada. Você pode misturar e combinar qualquer combinação de canais sem criar classes separadas para cada combinação!
Folha de consulta
O Decorator Pattern (Padrão Decorador) é um padrão de projeto estrutural que anexa novos comportamentos a objetos ao envolvê-los em objetos decoradores. Ele adiciona funcionalidade dinamicamente em tempo de execução sem modificar a classe original.
O padrão usa uma interface comum para que os objetos decorados possam ser tratados da mesma forma que o objeto original.
Estrutura Básica
Defina uma interface de componente:
interface Coffee {
String getDescription();
double getCost();
}Crie um componente concreto que implemente a interface:
class SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double getCost() {
return 2.0;
}
}Crie uma classe decoradora abstrata que implemente a mesma interface e mantenha uma referência ao objeto envolvido:
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}Crie decoradores concretos que estendam o decorador abstrato:
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public double getCost() {
return coffee.getCost() + 0.5;
}
}Usando Decoradores
Empilhe múltiplos decoradores para combinar funcionalidades:
Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);
System.out.println(order.getDescription()); // Simple Coffee, Milk, Sugar
System.out.println(order.getCost()); // 2.75O decorador delega chamadas ao objeto envolvido enquanto adiciona seu próprio comportamento. Cada decorador é independente e pode ser misturado e combinado livremente.
Experimente você mesmo
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Lê a mensagem a ser enviada
String message = scanner.nextLine();
// Lê a lista de canais separada por vírgulas (ex: "email,sms" ou "push,email,sms")
String channelsInput = scanner.nextLine();
// TODO: Começar com um BasicNotifier
// TODO: Dividir a entrada de canais por vírgula
// TODO: Percorrer cada canal e envolver o notifier com o decorador apropriado
// Canais válidos são: "email", "sms", "push"
// TODO: Chamar send no notifier totalmente decorado com a mensagem
}
}
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 ExternosIntrodução à POOClasses vs ObjetosA Palavra-chave thisMétodosCampos (Atributos)Método ConstrutorSobrecarga de ConstrutorRecapitulação - Calculadora Simples4Herança
Herança Básica (extends)A Palavra-chave superSobrescrita de Método (@Override)Encadeamento de ConstrutoresA Classe ObjectHerança Simples e MultinívelPor que não há Herança Múltipla de ClassesRecapitulação - Hierarquia de Funcionários7Métodos Especiais e a Classe Object
Método toString()equals() e hashCode()Método clone()compareTo() e ComparableInterface ComparatorRecapitulação - Ordenação Personalizada2Modificadores de Acesso e Encapsulamento
Visão Geral dos Níveis de AcessoMétodos Getter e SetterOcultação de InformaçõesA Palavra-chave finalRecapitulação - Gerenciador de Conta Bancária5Polimorfismo
Fundamentos de Sobrecarga de MétodosSobrescrita de Métodos (Tempo de Execução)Upcasting e DowncastingO Operador instanceofClasses e Métodos AbstratosRecapitulação - Calculadora de Formas8Conceitos Avançados de OOP
Composição vs HerançaAgregação vs ComposiçãoClasses Internas, Aninhadas e AnônimasEnums e Métodos de EnumRecords (Java 16+)Classes Seladas (Java 17+)11Padrões de Projeto Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão BuilderPadrão ObserverPadrão Strategy3Propriedades de Classe e Membros Estáticos
Variáveis de Instância vs EstáticasMétodos EstáticosBlocos EstáticosConstantes (static final)Recapitulação - Contador e Utilitário6Interfaces e Classes Abstratas
Introdução às InterfacesImplementando InterfacesImplementação de Múltiplas InterfacesDefault e Static em InterfacesClasses Abstratas vs InterfacesInterfaces FuncionaisRecapitulação - Sistema de Pagamento9Generics
Introdução a GenericsClasses GenéricasMétodos GenéricosParâmetros de Tipos DelimitadosWildcards (?, extends, super)Recapitulação - Container Genérico12Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositePadrão Iterator