Observer

El Patrón Observer: Una Relación de Dependencia Dinámica

El patrón Observer, también conocido como Publicador-Suscriptor, define una relación de uno a muchos entre objetos de tal manera que cuando un objeto cambia de estado, todos sus dependientes son notificados y actualizados automáticamente.

¿Cuándo utilizar el patrón Observer?

  • Notificaciones de cambios: Cuando un objeto necesita notificar a otros objetos sobre cambios en su estado.
  • Desacople: Cuando quieres desacoplar objetos para que no tengan un conocimiento directo unos de otros.
  • Distribución de eventos: Cuando necesitas distribuir eventos a múltiples objetos interesados.

Componentes clave del patrón Observer:

  • Sujeto (Subject): El objeto que mantiene una lista de sus observadores y notifica a todos cuando su estado cambia.
  • Observador (Observer): El objeto que se registra con un sujeto y es notificado cuando el estado del sujeto cambia.

Ejemplo práctico: Una aplicación de chat

Imagina una aplicación de chat. Los usuarios (observadores) se suscriben a una sala de chat (sujeto). Cuando un usuario envía un mensaje, la sala de chat notifica a todos los usuarios suscritos sobre el nuevo mensaje.

Implementación básica en pseudocodigo:

interface Observer {
    update(message: string): void;
}

class Subject {
    private observers: Observer[] = [];

    attach(observer: Observer) {
        this.observers.push(observer);
    }

    detach(observer: Observer)    {
        // ...
    }

    notify() {
        this.observers.forEach(observer => observer.update());
    }
}

Ventajas del patrón Observer:

  • Desacople: Los objetos no necesitan conocerse directamente, lo que facilita la modificación y el mantenimiento.
  • Flexibilidad: Se pueden agregar o eliminar observadores en tiempo de ejecución.
  • Reutilización: El patrón se puede aplicar en muchos escenarios diferentes.

Desventajas:

  • Complejidad: Puede introducir cierta complejidad en el diseño si se abusa de él.
  • Rendimiento: Notificar a muchos observadores puede tener un impacto en el rendimiento.

Consideraciones adicionales:

  • Rendimiento: Para optimizar el rendimiento, se pueden utilizar técnicas como la multidifusión o la publicación/suscripción.
  • Memoria: Una gran cantidad de observadores puede consumir mucha memoria.
  • Ciclos de referencia: Es importante evitar ciclos de referencia entre el sujeto y los observadores.

Descripción del Patrón Observer

La siguiente descripción simple del patrón observer es del libro Design Patterns

Intención Definir una dependencia de uno a muchos entre objetos de manera que cuando un objeto cambie de estado, todos sus dependientes sean notificados y actualizados automáticamente.

  1. Estructura


Participantes.

  1. Sujeto (Subject)
    1. Conoce a sus observadores. Cualquier número de objetos Observer puede observar a un sujeto.
    2. Proporciona una interfaz para adjuntar y separar objetos Observer.
  2. Observador (Observer)
    1. Define una interfaz de actualización para los objetos que deben ser notificados de cambios en un sujeto.
  3. Sujeto Concreto (ConcreteSubject)
    1. Almacena el estado de interés para los objetos ConcreteObserver.
    2. Envía una notificación a sus observadores cuando su estado cambia.
  4. Observador Concreto (ConcreteObserver)
    1. Mantiene una referencia a un objeto ConcreteSubject.
    2. Almacena el estado que debe mantenerse consistente con el del sujeto.
    3. Implementa la interfaz de actualización del Observer para mantener su estado consistente con el del sujeto.

Colaboración.


Ejemplo implementación en Python.

Python
class Observer:
    def update(self, message):
        pass

class Subject:
    def __init__(self):
        self._observers = []

    def attach(self, observer):
        self._observers.append(observer)

    def detach(self,    observer):
        self._observers.remove(observer)

    def notify(self, message):
        for observer in self._observers:
            observer.update(message)

class    ChatRoom(Subject):
    pass

class User(Observer):
    def __init__(self, name):
        self.name = name

    def update(self, message):
        print(f"{self.name}: {message}")

# Crear una sala de chat
chat_room = ChatRoom()

# Crear usuarios (observadores)
user1 = User("Alice")
user2 = User("Bob")
user3 = User("Charlie")

# Suscribir usuarios a la sala de chat
chat_room.attach(user1)
chat_room.attach(user2)
chat_room.attach(user3)

# Enviar un mensaje
chat_room.notify("Hola a todos!")

Explicación:

  1. Clases:

    • Observer: Define la interfaz que deben implementar todos los observadores.
    • Subject: Representa el sujeto que mantiene una lista de observadores y los notifica.
    • ChatRoom: Hereda de Subject y representa la sala de chat.
    • User: Implementa Observer y representa a un usuario que se suscribe a la sala de chat.
  2. Métodos:

    • attach y detach: Permiten agregar y eliminar observadores.
    • notify: Notifica a todos los observadores cuando ocurre un evento.
    • update: Método que se llama en cada observador cuando recibe una notificación.
  3. Ejemplo:

    • Se crea una instancia de ChatRoom.
    • Se crean instancias de User (Alice, Bob y Charlie).
    • Los usuarios se suscriben a la sala de chat.
    • Se envía un mensaje a través de notify, y cada usuario imprime el mensaje.

Cómo funciona:

Cuando se llama a chat_room.notify("Hola a todos!"), se itera sobre la lista de observadores y se llama al método update de cada uno, pasando el mensaje como argumento. Cada usuario imprime entonces el mensaje recibido.

Características clave de esta implementación:

  • Desacople: Los usuarios no necesitan conocer los detalles internos de la sala de chat.
  • Flexibilidad: Se pueden agregar o eliminar usuarios en cualquier momento.
  • Reutilización: Este patrón se puede aplicar a muchos otros escenarios, como eventos de interfaz de usuario, sistemas de notificación, etc.

Consideraciones adicionales:

  • Rendimiento: Para un gran número de observadores, podría ser más eficiente utilizar un mecanismo de publicación/suscripción basado en eventos.
  • Memoria: Asegúrate de gestionar correctamente las referencias para evitar fugas de memoria.
  • Hilos: Si estás trabajando con múltiples hilos, debes sincronizar el acceso a la lista de observadores.




Comentarios

Entradas más populares de este blog

Layers of Abstraction Architectural Pattern

Antipatrones de diseño de software

Asynchronous Message Communication Pattern