Padrão Observer (Eventos)
Parte da seção Programação Orientada a Objetos do Journey de C# da Coddy — lição 54 de 70.
O padrão Observer estabelece uma relação de um-para-muitos onde um objeto (o sujeito) notifica múltiplos objetos dependentes (observadores) quando seu estado muda. No C#, eventos fornecem uma maneira nativa de implementar este padrão de forma elegante.
O padrão envolve um publicador que dispara eventos e assinantes que respondem a eles. Aqui está um exemplo prático de um monitor de preços de ações:
public class Stock
{
private decimal _price;
public event Action<decimal> PriceChanged;
public decimal Price
{
get => _price;
set
{
_price = value;
PriceChanged?.Invoke(_price); // Notifica todos os observadores
}
}
}
public class PriceAlert
{
public void OnPriceChanged(decimal newPrice)
{
Console.WriteLine($"Alert: Price is now {newPrice}");
}
}
public class PriceLogger
{
public void OnPriceChanged(decimal newPrice)
{
Console.WriteLine($"Logged: {newPrice}");
}
}Os assinantes vinculam seus métodos ao evento usando += e podem desvinculá-los com -=:
var stock = new Stock();
var alert = new PriceAlert();
var logger = new PriceLogger();
stock.PriceChanged += alert.OnPriceChanged;
stock.PriceChanged += logger.OnPriceChanged;
stock.Price = 150.50m;
// Saída:
// Alerta: Price is now 150.50
// Registrado: 150.50
stock.PriceChanged -= logger.OnPriceChanged; // Cancelar inscrição
stock.Price = 155.00m;
// Saída: Alerta: Price is now 155.00O padrão Observer promove o baixo acoplamento - a classe Stock não precisa saber nada sobre seus observadores. Novos observadores podem se inscrever sem modificar o publicador, tornando o sistema fácil de estender e manter.
Desafio
FácilVamos construir um sistema de monitoramento de temperatura usando o padrão Observer com eventos. Você criará um sensor que notifica múltiplos observadores sempre que a temperatura mudar - perfeito para cenários como sistemas de controle climático onde diferentes componentes precisam reagir a atualizações de temperatura.
Você organizará seu código em três arquivos:
TemperatureSensor.cs: Crie uma classeTemperatureSensorno namespaceMonitoring. Este é o seu publicador - o assunto que outros objetos irão observar. Seu sensor deve ter:- Um campo privado para armazenar a temperatura atual
- Um evento chamado
TemperatureChangedque usaAction<double>para notificar os assinantes sobre a nova temperatura - Uma propriedade
Temperatureque dispara o evento sempre que a temperatura for definida para um novo valor
Observers.cs: Crie duas classes observadoras no mesmo namespace que assinarão as mudanças de temperatura:DisplayUnit- possui um métodoOnTemperatureChanged(double temp)que imprimeDisplay: {temp}CAlarmSystem- possui um métodoOnTemperatureChanged(double temp)que imprimeAlarm Check: {temp}C
Program.cs: Reúna tudo criando um sensor e ambos os observadores. Assine ambos os observadores no evento do sensor e, em seguida, atualize a temperatura. Depois disso, cancele a assinatura do sistema de alarme e atualize a temperatura novamente para mostrar que apenas a unidade de exibição recebe a notificação.
Você receberá duas entradas:
- Primeira leitura de temperatura (ex:
22.5) - Segunda leitura de temperatura (ex:
28.0)
Seu programa deve:
- Criar o sensor e ambos os observadores
- Assinar ambos os observadores no evento
TemperatureChanged - Definir a temperatura do sensor para o primeiro valor de entrada (ambos os observadores devem ser notificados)
- Cancelar a assinatura do
AlarmSystemdo evento - Definir a temperatura do sensor para o segundo valor de entrada (apenas o display deve ser notificado)
Por exemplo, se as entradas forem 22.5 e 28.0, a saída deve ser:
Display: 22.5C
Alarm Check: 22.5C
Display: 28CIsso demonstra o poder central do padrão Observer - o sensor não sabe nem se importa com quem o está ouvindo. Os observadores podem assinar e cancelar a assinatura dinamicamente, e o publicador simplesmente notifica quem estiver ouvindo no momento!
Folha de consulta
O padrão Observer estabelece uma relação de um para muitos onde um objeto (o sujeito) notifica múltiplos objetos dependentes (observadores) quando seu estado muda.
No C#, eventos fornecem uma maneira integrada de implementar este padrão. O padrão envolve um publicador (publisher) que dispara eventos e assinantes (subscribers) que respondem a eles.
Implementação Básica de Eventos
Defina um evento usando event Action<T> e invoque-o quando o estado mudar:
public class Stock
{
private decimal _price;
public event Action<decimal> PriceChanged;
public decimal Price
{
get => _price;
set
{
_price = value;
PriceChanged?.Invoke(_price); // Notifica todos os observadores
}
}
}Criando Observadores
Observadores são classes com métodos que correspondem à assinatura do evento:
public class PriceAlert
{
public void OnPriceChanged(decimal newPrice)
{
Console.WriteLine($"Alert: Price is now {newPrice}");
}
}
public class PriceLogger
{
public void OnPriceChanged(decimal newPrice)
{
Console.WriteLine($"Logged: {newPrice}");
}
}Inscrevendo e Cancelando a Inscrição
Use += para se inscrever e -= para cancelar a inscrição em eventos:
var stock = new Stock();
var alert = new PriceAlert();
var logger = new PriceLogger();
stock.PriceChanged += alert.OnPriceChanged; // Inscrever
stock.PriceChanged += logger.OnPriceChanged; // Inscrever
stock.Price = 150.50m; // Ambos os observadores notificados
stock.PriceChanged -= logger.OnPriceChanged; // Cancelar inscrição
stock.Price = 155.00m; // Apenas o alerta notificadoO padrão Observer promove o baixo acoplamento - o publicador não precisa saber nada sobre seus observadores. Novos observadores podem se inscrever sem modificar o publicador.
Experimente você mesmo
using System;
using Monitoring;
class Program
{
public static void Main(string[] args)
{
// Ler as temperaturas de entrada
double temp1 = Convert.ToDouble(Console.ReadLine());
double temp2 = Convert.ToDouble(Console.ReadLine());
// TODO: Criar uma instância de TemperatureSensor
// TODO: Criar instâncias de DisplayUnit e AlarmSystem
// TODO: Inscrever ambos os observadores no evento TemperatureChanged
// TODO: Definir a temperatura do sensor para temp1 (ambos os observadores notificados)
// TODO: Cancelar a inscrição do AlarmSystem no evento
// TODO: Definir a temperatura do sensor para temp2 (apenas o display notificado)
}
}
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