Patrón Observer (Eventos)
Parte de la sección Programación Orientada a Objetos del Journey de C# de Coddy — lección 54 de 70.
El patrón Observer establece una relación de uno a muchos donde un objeto (el sujeto) notifica a múltiples objetos dependientes (observadores) cuando su estado cambia. En C#, los eventos proporcionan una forma integrada de implementar este patrón de manera elegante.
El patrón involucra a un publicador que emite eventos y suscriptores que responden a ellos. Aquí hay un ejemplo práctico de un monitor de precios de acciones:
public class Stock
{
private decimal _price;
public event Action<decimal> PriceChanged;
public decimal Price
{
get => _price;
set
{
_price = value;
PriceChanged?.Invoke(_price); // Notificar a todos los 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}");
}
}Los suscriptores asocian sus métodos al evento utilizando += y pueden desvincularlos con -=:
var stock = new Stock();
var alert = new PriceAlert();
var logger = new PriceLogger();
stock.PriceChanged += alert.OnPriceChanged;
stock.PriceChanged += logger.OnPriceChanged;
stock.Price = 150.50m;
// Salida:
// Alerta: El precio ahora es 150.50
// Registrado: 150.50
stock.PriceChanged -= logger.OnPriceChanged; // Cancelar suscripción
stock.Price = 155.00m;
// Salida: Alerta: El precio ahora es 155.00El patrón Observer promueve el acoplamiento débil: la clase Stock no necesita saber nada sobre sus observadores. Nuevos observadores pueden suscribirse sin modificar al publicador, lo que hace que el sistema sea fácil de extender y mantener.
Desafío
FácilVamos a construir un sistema de monitoreo de temperatura utilizando el patrón Observer con eventos. Crearás un sensor que notifica a múltiples observadores cada vez que la temperatura cambia, lo cual es perfecto para escenarios como sistemas de control climático donde diferentes componentes necesitan reaccionar a las actualizaciones de temperatura.
Organizarás tu código en tres archivos:
TemperatureSensor.cs: Crea una claseTemperatureSensoren el espacio de nombresMonitoring. Este es tu publicador: el sujeto que otros objetos observarán. Tu sensor debe tener:- Un campo privado para almacenar la temperatura actual
- Un evento llamado
TemperatureChangedque utilizaAction<double>para notificar a los suscriptores sobre la nueva temperatura - Una propiedad
Temperatureque activa el evento cada vez que se establece la temperatura a un nuevo valor
Observers.cs: Crea dos clases observadoras en el mismo espacio de nombres que se suscribirán a los cambios de temperatura:DisplayUnit: tiene un métodoOnTemperatureChanged(double temp)que imprimeDisplay: {temp}CAlarmSystem: tiene un métodoOnTemperatureChanged(double temp)que imprimeAlarm Check: {temp}C
Program.cs: Une todo creando un sensor y ambos observadores. Suscribe ambos observadores al evento del sensor y luego actualiza la temperatura. Después de eso, desuscribe el sistema de alarma y actualiza la temperatura nuevamente para mostrar que solo la unidad de visualización recibe la notificación.
Recibirás dos entradas:
- Primera lectura de temperatura (por ejemplo,
22.5) - Segunda lectura de temperatura (por ejemplo,
28.0)
Tu programa debe:
- Crear el sensor y ambos observadores
- Suscribir ambos observadores al evento
TemperatureChanged - Establecer la temperatura del sensor al primer valor de entrada (ambos observadores deben ser notificados)
- Desuscribir el
AlarmSystemdel evento - Establecer la temperatura del sensor al segundo valor de entrada (solo el display debe ser notificado)
Por ejemplo, si las entradas son 22.5 y 28.0, la salida debería ser:
Display: 22.5C
Alarm Check: 22.5C
Display: 28CEsto demuestra el poder central del patrón Observer: el sensor no sabe ni le importa quién lo está escuchando. Los observadores pueden suscribirse y desuscribirse dinámicamente, ¡y el publicador simplemente notifica a quien esté escuchando en ese momento!
Hoja de referencia
El patrón Observer establece una relación de uno a muchos donde un objeto (el sujeto) notifica a múltiples objetos dependientes (observadores) cuando su estado cambia.
En C#, los eventos proporcionan una forma integrada de implementar este patrón. El patrón involucra a un publicador que emite eventos y suscriptores que responden a ellos.
Implementación básica de eventos
Define un evento usando event Action<T> e invócalo cuando el estado cambie:
public class Stock
{
private decimal _price;
public event Action<decimal> PriceChanged;
public decimal Price
{
get => _price;
set
{
_price = value;
PriceChanged?.Invoke(_price); // Notificar a todos los observadores
}
}
}Creación de observadores
Los observadores son clases con métodos que coinciden con la firma del 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}");
}
}Suscripción y cancelación de suscripción
Usa += para suscribirte y -= para cancelar la suscripción a los eventos:
var stock = new Stock();
var alert = new PriceAlert();
var logger = new PriceLogger();
stock.PriceChanged += alert.OnPriceChanged; // Suscribirse
stock.PriceChanged += logger.OnPriceChanged; // Suscribirse
stock.Price = 150.50m; // Ambos observadores notificados
stock.PriceChanged -= logger.OnPriceChanged; // Cancelar suscripción
stock.Price = 155.00m; // Solo la alerta es notificadaEl patrón Observer promueve el acoplamiento débil: el publicador no necesita saber nada sobre sus observadores. Nuevos observadores pueden suscribirse sin modificar el publicador.
Pruébalo tú mismo
using System;
using Monitoring;
class Program
{
public static void Main(string[] args)
{
// Leer las temperaturas de entrada
double temp1 = Convert.ToDouble(Console.ReadLine());
double temp2 = Convert.ToDouble(Console.ReadLine());
// TODO: Crear una instancia de TemperatureSensor
// TODO: Crear instancias de DisplayUnit y AlarmSystem
// TODO: Suscribir ambos observadores al evento TemperatureChanged
// TODO: Establecer la temperatura del sensor a temp1 (ambos observadores notificados)
// TODO: Desuscribir el AlarmSystem del evento
// TODO: Establecer la temperatura del sensor a temp2 (solo el display notificado)
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosNamespaces y directivasIntro a clases y objetosLa palabra clave 'this'Métodos y parámetrosCampos vs PropiedadesConstructoresInicializadores de objetosResumen - Calculadora simple4Herencia
Sintaxis básica de herencia (:)La palabra clave 'base'Palabras clave Virtual y OverrideClases selladas (Sealed)La clase base 'object'Resumen: Jerarquía de empleados7Características avanzadas
Sobrecarga de operadoresIndexadores (this[])Sobrescritura de ToString()Métodos de extensiónResumen - Lista personalizada10Patrones de diseño - Parte 1
Introducción a los patrones de diseñoSingleton Thread-SafePatrón FactoryPatrón Observer (Eventos)Patrón Strategy2Propiedades y miembros estáticos
Propiedades autoimplementadasPropiedades de solo lectura y escrituraCampos y métodos estáticosClases estáticasMiembros con cuerpo de expresión5Polimorfismo e Interfaces
Polimorfismo: Compilación vs. EjecuciónInterfaz vs. Clase AbstractaInterfaces MúltiplesInterfaces ExplícitasUpcasting y DowncastingRepaso: Calculadora de Figuras8Conceptos avanzados de POO
Composición sobre herenciaGenéricos (clases y métodos)Delegados y eventosAtributos y ReflexiónIDisposable y la sentencia usingFundamentos de Inyección de dependencias11Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite3Arquitectura de clases
Datos de instancia vs. estáticosPalabras clave 'readonly' y 'const'Campos de respaldoResumen - Gestor de cuentas bancarias6Encapsulamiento
Modificadores de accesoPropiedades para el encapsulamientoImplementación de ocultamiento de datosPatrones de inmutabilidadRepaso - Registros de estudiantes