Menú Cerrar

Patrón Observer en C#

Patrón Observer en C#

Este artículo, sobre el patrón Observer en C#, hace parte del segundo calendario de adviento de C# en español. Cada día, durante 25 días, serán compartidos 1 o 2 artículos sobre C#. Esta es mi contribución al día 23. En el primer calendario también participé.


El patrón de diseño Observer es uno de los más comunes que te podrás encontrar en cualquier lenguaje de programación. La razón es que se utiliza muchísimo en interfaces gráficas, sin importar que sean web, móvil o de escritorio.

En este artículo te contaré los aspectos básicos del patrón Observer, luego te mostraré un par de implementaciones en C#.

NOTA. Si no estás familiarizado con los patrones de diseño, te recomiendo mi video introductorio sobre el tema.

Introducción al patrón Observer

Observer es uno de los 23 patrones de diseño definidos por The Gang of Four en su libro original sobre el tema. Observer es un patrón de comportamiento; es decir está enfocado en modelar un algoritmo.

¿Cuándo usarías Observer?

Principalmente, cuando tienes uno o varios objetos, llamados observadores, que desean ser notificados cuando algo ocurre en cierto objeto, al cual llamaremos sujeto. Ese “algo” es lo que llamamos un evento.

Ejemplos:

  • Tienes que ejecutar un bloque de código (observador) cada vez que un botón (sujeto) es presionado (evento).
  • Tienes una clase (observador) que debe enviar un correo electrónico cada vez que un usuario es creado (evento) en cierto servicio (sujeto).

A nivel de clases, así es como se ve este patrón:

Estructura de clases del patrón Observer.

Demos un repaso a cada una de las parte involucradas:

  • Sujeto. Es una interfaz o clase abstracta para todas clases que puedan emitir eventos. Es opcional tenerlo.
  • Sujeto concreto. Representa la clase concreta que emitirá un evento. Debe tener métodos para suscribirse o desuscribirse a las notificaciones. Además, debe ofrecer algún mecanismo para notificar que el evento ocurrió.
  • Observador. Es la interfaz base para todas las clases que están interesadas en escuchar eventos.
  • Observador concreto. Es la clase que tiene la lógica de negocio que se ejecutará cuando el evento ocurra.

Implementación básica del patrón Observer en C#

Ahora, te mostraré como hacer la implementación del patrón Observer en C#, usando funcionalidades básicas del lenguaje y lo más parecido a la idea original de The Gang of Four.

Empecemos definiendo las interfaces para sujetos y observadores. Además, creamos una clase que nos represente el evento que emitirá el sujeto.

Interfaces

Observador (Interfaz IObserver)

interface IObserver
{
    void Update(SubjectEvent subjectEvent);
}

El observador debe definir el método que será llamado cuando el evento ocurra.

Sujeto (Interfaz ISubject)

interface ISubject
{
   void Subscribe(IObserver observer);

   void Unsubscribe(IObserver observer);

   void NotifyObservers(SubjectEvent subjectEvent);
}

Nota como los métodos para suscribirse y desuscribirse reciben objetos de tipo IObserver. Adicional a eso, hay un método para notificar a los observadores. Este último es opcional que esté en la interfaz, y podría crearse de manera privada en el observador concreto.

Si solamente se va a tener un tipo de sujeto, no es necesario tener esta interfaz.

Evento

class SubjectEvent
 {
    public string EventType { get; set; }
    public DateTime EventDate { get; set; }
 }

En lo personal, prefiero siempre tener una clase que represente el evento, ya que así es más fácil encapsular la información.

Clases concretas

Ya tienes las interfaces. Es hora de implementar las clases concretas.

Empecemos por el observador concreto.

class ConsoleObserver : IObserver
{
    public void Update(SubjectEvent subjectEvent)
    {
       Console.WriteLine("An event just happened!");
       Console.WriteLine("Event type: " + subjectEvent.EventType);
       Console.WriteLine("Date: " + subjectEvent.EventDate);
    }
}

La lógica que pondrás en el observador concreto depende específicamente del problema. En mi caso, solo quería un observador que registrara por consola los eventos en la medida que ocurrieran.

Ahora vamos con la clase más interesante: el sujeto concreto.

class ProductService : ISubject
{
    private readonly IList observers;
    public ProductService() {
       observers = new List();
    }

    public void Subscribe(IObserver observer) {
        observers.Add(observer);
    }
 
    public void Unsubscribe(IObserver observer) {
        observers.Remove(observer);
    } 
    
    public void AddProduct(string productName) {
        //Business logic to validate and add a product.
        var subjectEvent = new SubjectEvent { 
            EventType = "ProductAdded",
            EventDate = DateTime.Now
        };
        NotifyObservers(subjectEvent);
    }

    public void NotifyObservers(SubjectEvent subjectEvent) {
        Console.WriteLine("Before notifying observers");
        foreach(IObserver observer in observers) {
            observer.Update(subjectEvent);
        }
        Console.WriteLine("After notifying observers");
     }
 }

Describamos lo que sucede:

  • En las líneas 3 y 5 estamos creando e inicializando una colección donde guardaremos los observadores interesados en escuchar el evento.
  • En línea 8, sobreescribimos el método que nos permitirá registrar un observador.
  • En la línea 12, definimos el método que nos permitirá desuscribir un observador en caso de que no queramos seguir escuchando eventos. Esta es una de las ventajas del patrón Observer.
  • En línea 16 tenemos un método con algo de lógico de negocio (varía según el problema). Lo importante es la línea 22, donde llamamos al método que notificará a todos los observadores registrados.
  • En la línea 25 está el método que notificará a todos los observadores. Gracias a que estamos usando interfaces, en la línea 28 podemos llamar el método Update.

Esto último es que lo se conoce como programar contra la abstracción. Si deseas aprender más de este concepto, te recomiendo mi mini curso gratuito de Programación Profesional con Objetos.

Conectemos todas las piezas

Para esto, tendremos una clase ejecutable donde crearemos los sujetos y los observadores.

class Program {
      static void Main(string[] args)
      {
            IObserver observer = new ConsoleObserver();
            ProductService subject = new ProductService();
            subject.Subscribe(observer);
            subject.AddProduct("Solid-state drive");

            Console.WriteLine();
            subject.Unsubscribe(observer);
            subject.AddProduct("Bluetooth mouse");
      }
}

Otras maneras implementar el patrón

Lo que acabamos de ver es la manera más clásica de implementar el patrón Observer en C#. Sin embargo, existen otras formas.

Una de ellas es utilizar delegados y eventos. De hecho, estos son los elementos del lenguaje que se usan en tecnologías como Windows Forms para proveer este patrón.

Otra manera es utilizar la interfaces genéricas IObserver y IObservable del API de .NET.

Cierre

El patrón Observer es uno de los más útiles y comunes que nos encontraremos en nuestro trabajo como desarrolladores. Nos permite extender nuestro código, garantizando el bajo acoplamiento.

Espero que te haya gustado este artículo.


Suscríbete a mi lista de correo.

Te avisaré de nuevo material que te ayude a ser un mejor desarrollador o arquitecto.
Posted in Patrones de diseño