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 en la que un objeto (el sujeto) notifica a varios objetos dependientes (observadores) cuando cambia su estado. En C#, los eventos ofrecen una forma integrada de implementar este patrón con elegancia.
El patrón incluye un publicador que genera eventos y suscriptores que responden a ellos. Aquí tienes 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); // Notifica 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 mediante += y pueden desasociarlos 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 es ahora 150.50
// Registrado: 150.50
stock.PriceChanged -= logger.OnPriceChanged; // Cancelar la suscripción
stock.Price = 155.00m;
// Salida: Alerta: El precio es ahora 155.00El patrón Observer fomenta un acoplamiento débil: la clase Stock no necesita saber nada sobre sus observadores. Los nuevos observadores pueden suscribirse sin modificar el publicador, lo que facilita ampliar y mantener el sistema.
Desafío
FácilVamos a crear un sistema de monitoreo de temperatura usando el patrón Observer con eventos. Crearás un sensor que notifica a varios observadores cada vez que cambia la temperatura, perfecto para situaciones como los sistemas de control climático, donde distintos componentes deben reaccionar a las actualizaciones de temperatura.
Organizarás el código en tres archivos:
TemperatureSensor.cs: Crea una claseTemperatureSensoren el espacio de nombresMonitoring. Este es tu publicador: el sujeto que observarán otros objetos. Tu sensor debe tener:- Un campo privado para almacenar la temperatura actual
- Un evento llamado
TemperatureChangedque useAction<double>para notificar a los suscriptores sobre la nueva temperatura - Una propiedad
Temperatureque genere el evento cada vez que se establezca la temperatura en 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: Combina todo creando un sensor y ambos observadores. Suscribe a ambos observadores al evento del sensor y luego actualiza la temperatura. Después, cancela la suscripción del sistema de alarma y vuelve a actualizar la temperatura para mostrar que solo la unidad de visualización recibe la notificación.
Recibirás dos valores de entrada:
- 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 a ambos observadores al evento
TemperatureChanged - Establecer la temperatura del sensor en el primer valor de entrada (se debe notificar a ambos observadores)
- Cancelar la suscripción de
AlarmSystemal evento - Establecer la temperatura del sensor en el segundo valor de entrada (solo se debe notificar a la unidad de visualización)
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 fundamental del patrón Observer: al sensor no le importa ni necesita saber quién lo está escuchando. Los observadores pueden suscribirse y cancelar su suscripción dinámicamente, y el publicador simplemente notifica a quienes estén escuchando en ese momento.
Pruébalo tú mismo
using System;
using Monitoring;
class Program
{
public static void Main(string[] args)
{
// Lee las temperaturas de entrada
double temp1 = Convert.ToDouble(Console.ReadLine());
double temp2 = Convert.ToDouble(Console.ReadLine());
// TODO: Crea una instancia de TemperatureSensor
// TODO: Crea instancias de DisplayUnit y AlarmSystem
// TODO: Suscribe ambos observadores al evento TemperatureChanged
// TODO: Establece la temperatura del sensor en temp1 (se notifica a ambos observadores)
// TODO: Cancela la suscripción de AlarmSystem al evento
// TODO: Establece la temperatura del sensor en temp2 (solo se notifica a la pantalla)
}
}
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 estudiantesPractica por tu cuenta: Compilador de C# online