El Patrón Observador, también conociod como Patrón Publicador-Suscriptor, establece una relación de dependencia uno a muchos. Permite que múltiples objetos (observadores) monitoricen un objeto central (sujeto). Cuando el estado del sujeto cambia, notifica a todos sus observadores para que se actualicen automáticamente. Este patrón es una manifestación del Principio de Inversión de Dependencia.
Implementación con Delegados y Eventos en C#
La implementación tradicional del Patrón Observador puede volverse verbosa. C# ofrece mecanismos como delegados y eventos que facilitan la creación de sistemas de notificación desacoplados de manera más concisa.
Definición del Sujeto (Subject)
El sujeto es responsable de mantener la lista de observadores y notificarles sobre los cambios de estado.
interface ISubject
{
void RegisterObserver(IObserver observer);
void RemoveObserver(IObserver observer);
void NotifyObservers();
string State { get; set; }
}
// Implementación concreta del Sujeto (Ej: Jefe)
class Boss : ISubject
{
private readonly IList<iobserver> _observers = new List<iobserver>();
private string _currentState;
public void RegisterObserver(IObserver observer)
{
_observers.Add(observer);
}
public void RemoveObserver(IObserver observer)
{
_observers.Remove(observer);
}
public void NotifyObservers()
{
foreach (var observer in _observers)
{
observer.Update(this);
}
}
public string State
{
get { return _currentState; }
set
{
_currentState = value;
Console.WriteLine($"El estado del jefe ha cambiado a: '{_currentState}'");
NotifyObservers();
}
}
}
// Otra implementación concreta del Sujeto (Ej: Secretaria)
class Secretary : ISubject
{
private readonly IList<iobserver> _observers = new List<iobserver>();
private string _currentState;
public void RegisterObserver(IObserver observer)
{
_observers.Add(observer);
}
public void RemoveObserver(IObserver observer)
{
_observers.Remove(observer);
}
public void NotifyObservers()
{
foreach (var observer in _observers)
{
observer.Update(this);
}
}
public string State
{
get { return _currentState; }
set
{
_currentState = value;
Console.WriteLine($"El estado de la secretaria ha cambiado a: '{_currentState}'");
NotifyObservers();
}
}
}
</iobserver></iobserver></iobserver></iobserver>
Definición del Observador (Observer)
Los observadores reaccionan a los cambios en el estado del sujeto.
// Interfaz abstracta del Observador
abstract class Observer
{
protected string Name { get; }
protected ISubject Subject { get; }
protected Observer(string name, ISubject subject)
{
Name = name;
Subject = subject;
}
public abstract void Update();
}
// Implementación concreta del Observador (Ej: Empleado que mira acciones)
class StockObserver : Observer
{
public StockObserver(string name, ISubject subject) : base(name, subject)
{
}
public override void Update()
{
Console.WriteLine($"¡{Subject.State}! {Name} deja de mirar las acciones y vuelve al trabajo.");
}
}
Cliente (Client)
El código del cliente configura la relación entre el sujeto y sus observadores.
static class Client
{
static void Main(string[] args)
{
// Crear el sujeto (Jefe)
var boss = new Boss();
// Crear observadores (Empleados)
var colleague1 = new StockObserver("Juan Pérez", boss);
var colleague2 = new StockObserver("María García", boss);
// Registrar los observadores
boss.RegisterObserver(colleague1);
boss.RegisterObserver(colleague2);
// Cambiar el estado del sujeto y notificar a los observadores
boss.State = "El jefe ha regresado a la oficina";
Console.WriteLine("\n------------------------------------\n");
// Crear otro sujeto (Secretaria)
var secretary = new Secretary();
var anotherColleague = new StockObserver("Carlos López", secretary);
secretary.RegisterObserver(anotherColleague);
// Cambiar el estado de la secretaria
secretary.State = "La reunión ha terminado";
Console.ReadKey();
}
}
Ventajas del Patrón Observador
El Patrón Observador se centra en el desacoplamiento. Al hacer que tanto el sujeto como los observadores dependan de abstracciones (interfaces) en lugar de implementaciones concretas, los cambios en uno no impactan directamente al otro. Esto promueve un código más flexible y mantenible.
Implementación Simplificada con Eventos en C#
C# permite una implementación aún más limpia del Patrón Observador utilizando delegados y eventos, eliminando la necesidad de la interfaz ISubject explícita en el observador y la gestión manual de la lista de observadores en el sujeto.
Sujeto con Eventos
// Delegado para el evento de notificación
public delegate void StateChangedEventHandler(string newState);
class BossWithEvents
{
// Evento que los observadores pueden suscribir
public event StateChangedEventHandler StateChanged;
private string _currentState;
public string CurrentState
{
get { return _currentState; }
set
{
_currentState = value;
Console.WriteLine($"El jefe informa: '{_currentState}'");
OnStateChanged(_currentState); // Notificar a los suscriptores
}
}
// Método para invocar el evento
protected virtual void OnStateChanged(string newState)
{
// Solo invoca el evento si hay suscriptores
StateChanged?.Invoke(newState);
}
}
Observador que Reacciona a Eventos
class Employee
{
public string Name { get; }
public Employee(string name)
{
Name = name;
}
// Método que se ejecutará cuando el evento sea invocado
public void HandleBossStateChange(string newState)
{
Console.WriteLine($"{Name}: {newState} - ¡Dejo de mirar el móvil y vuelvo al trabajo!");
}
}
Cliente Utilizando Eventos
static class EventClient
{
static void Main(string[] args)
{
var boss = new BossWithEvents();
var emp1 = new Employee("Ana López");
var emp2 = new Employee("Luis Torres");
// Suscribir los métodos de los empleados al evento del jefe
// Es importante notar que el método suscrito puede pertenecer a una clase diferente.
boss.StateChanged += emp1.HandleBossStateChange;
boss.StateChanged += emp2.HandleBossStateChange;
// Cambiar el estado del jefe, lo que disparará el evento
boss.CurrentState = "¡El jefe ha vuelto!";
Console.WriteLine("\n------------------------------------\n");
// Para desuscribir:
// boss.StateChanged -= emp1.HandleBossStateChange;
Console.Read();
}
}
La utilización de eventos en C# simplifica significativamente la implementación del Patrón Observador, haciendo el código más legible y menos propenso a errores relacionados con la gestión manual de listas de suscriptores.