Patrón de diseño de observador en Python

El patrón de diseño del observador es un sistema basado en eventos, donde un observable envía mensajes a los observadores que están suscritos. ¡Implementemos este patrón en Python!

Introducción

Los Patrones de diseño de software ayudan a acelerar el proceso de desarrollo al proporcionar un modelo reutilizable para que su código resuelva un problema en particular. Seguimos los patrones de diseño para escribir código generalizado, reutilizable y legible que otras personas familiarizadas con los patrones que hemos aplicado puedan entender fácilmente.

Encapsulan la experiencia acumulada de los ingenieros de software que resuelven los mismos problemas y representan soluciones a problemas comunes relacionados con el diseño.

Existen diferentes clasificaciones de patrones de diseño según la clase de problemas que resuelven, entre los cuales el Patrón de diseño del observador pertenece a la clase Patrón de comportamiento.

Esta clase de patrones determina cómo se comunican los objetos entre sí. En esta guía, aprenderá todo lo que necesita saber sobre el patrón de diseño del observador y comprenderá cómo podemos usarlo para resolver ciertos problemas de manera eficiente.

Patrón de diseño de observador

El Patrón de diseño del observador se ocupa de las relaciones Uno a muchos y utiliza eventos para informar a las entidades suscritas sobre los cambios en un observable.

La fuente de estos eventos se denomina sujeto u observable que envía eventos como secuencias. Los observadores o sumideros pueden suscribirse al observable para obtener los eventos. El observable realiza un seguimiento de la lista de observadores y les notifica los cambios cuando cambia el estado del observable.

Diagrama de arquitectura del patrón de diseño del observador: muestra una fuente de noticias como observable y múltiples lectores como observadores suscritos a la fuente de noticias

Esta funcionalidad tiene muchas implicaciones e implementaciones, y una funcionalidad similar está a su alrededor. Es un patrón extremadamente simple, pero muy efectivo y muy extendido.

Una implementación similar de este patrón de diseño se ve en la generación de feeds en sus plataformas sociales: el modelo/patrón Pub/Sub (editor/suscriptor). Cuando un editor de contenido publica sus publicaciones, los suscriptores reciben una notificación del contenido. Una analogía similar puede ser que las personas busquen una señal de bengala o fuegos artificiales para un determinado evento y reaccionen (o no) según sus roles específicos.

¿Significa eso que el Patrón de diseño de observador y el Patrón de publicación/suscripción son iguales?

Anteriormente, ambos patrones eran sinónimos. Hoy en día, cada patrón tiene rasgos distintos que los convierten en dos patrones separados.

Las siguientes son las principales diferencias entre el patrón Observer y el patrón Pub/Sub:

  • Los Observadores y los Sujetos están estrechamente acoplados. Los sujetos deben hacer un seguimiento de sus observadores. Mientras que en el patrón Pub/Sub, se acoplan libremente con una cola de mensajes entre observadores y sujetos.
  • Los eventos se pasan de manera sincrónica de los Sujetos a los Observadores. Pero en los patrones de Pub/Sub, los eventos se transmiten de forma asíncrona.
  • En el patrón Observer, tanto los Sujetos como los Observadores residen en la misma localidad de la aplicación, mientras que pueden residir en diferentes localidades en el patrón Pub/Sub.

Una de las mejores maneras de tener una idea de este patrón es implementarlo, ¡hagámoslo en Python!

Implementación

Una implementación básica requiere dos clases: un Observable y un Observer. La clase Observer se inicializa con un objeto como argumento. El objeto no es otro que un ‘Observable’ del que se debe realizar un seguimiento, al que se suscribe en el momento de la creación.

La clase también tiene una función notify() que activa una reacción y reconoce la recepción de una notificación/evento del observable:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Observer:

    def __init__(self, observable):
        observable.subscribe(self)

    def notify(
        self,
        observable,
        *args,
        **kwargs
        ):
        print ('Got', args, kwargs, 'From', observable)

La clase Observable se inicializa con una lista vacía para contener las instancias Observador. También tiene funciones como subscribe() para agregar un observador, notify_observers() para llamar a la función notify() en cada observador y unsubscribe() para eliminar al observador de la lista:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Observable:

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

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

    def notify_observers(self, *args, **kwargs):
        for obs in self._observers:
            obs.notify(self, *args, **kwargs)

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

Conectando todos los componentes mencionados anteriormente, escribamos un código que configure un observador y observable y envíe mensajes, lo que desencadena una reacción:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# observer_pattern.py

"""
Demonstrating the Observer pattern implementation
"""
# Initializing the subject
subject = Observable()

# Initializing twp observers with the subject object
observer1 = Observer(subject)
observer2 = Observer(subject)

# The following message will be notified to 2 observers
subject.notify_observers('This is the 1st broadcast',
                         kw='From the Observer')
subject.unsubscribe(observer2)

# The following message will be notified to just 1 observer since
# the observer has been unsubscribed
subject.notify_observers('This is the 2nd broadcast',
                         kw='From the Observer')

Tenga en cuenta que también cancelamos la suscripción de un observador antes de publicar el segundo mensaje. Esto hará que el mensaje se imprima solo una vez en lugar de dos veces en el segundo intento, ya que solo lo recibe un suscriptor.

Ejecutar este código dará como resultado:

1
2
3
4
$ python observer_pattern.py
Got ('This is the 1st broadcast',) {'kw': 'From the Observer'} From <__main__.Observable object at 0x7f6c50d2fb50>
Got ('This is the 1st broadcast',) {'kw': 'From the Observer'} From <__main__.Observable object at 0x7f6c50d2fb50>
Got ('This is the 2nd broadcast',) {'kw': 'From the Observer'} From <__main__.Observable object at 0x7f6c50d2fb50>

Como puede ver, el observable puede interactuar directamente con los observadores y viceversa. El observable estará en interacción con el observador siempre que el observador esté suscrito a la lista de suscripción del observable.

Ventajas y desventajas

Con la implementación en su lugar, los pros y los contras de este patrón de diseño se pueden comparar de la siguiente manera:

Pros:

  • La relación uno a muchos se define entre los objetos. Esto asegura que cuando se modifica un objeto, dará lugar a una cascada de cambios que se aplicarán a los objetos dependientes.

  • Los objetos acoplados libremente significan que los componentes se pueden intercambiar.

Contras:

  • La comunicación entre el observable y el observador es sincrónica y con una mayor carga de eventos de suscripción y cancelación de suscripción, el objeto observable podría verse bombardeado con solicitudes. Esto podría mitigarse estableciendo un tiempo de sueño para cada solicitud.

  • La solución de suspensión también podría causar una posible pérdida de velocidad, rendimiento y eventos. Esta fue la razón principal por la que el patrón Pub/Sub tenía una cola de mensajes entre el editor y el suscriptor.

  • Las fugas de memoria son comunes en este patrón ya que existe una fuerte referencia entre el observador y el observable. Los observables deben ser obligatoriamente dados de baja del objeto observable.

Para mitigar la mayoría de las desventajas, se introdujo una cola de mensajes entre el observador y el observable para superar todos estos problemas, lo que llevó a diseñar el patrón Pub/Sub, una variación del patrón Observer.

Conclusión

Esta guía cubre el patrón Observer, cómo se puede implementar y compara sus ventajas y desventajas.

Es interesante notar que el patrón de observador es uno de los patrones de comportamiento que ha dado lugar a muchas de las funciones que usamos hoy en día, como fuentes RSS, fuentes de redes sociales, etc.

Al conocer los matices de los patrones de diseño, es más fácil construir la funcionalidad desde cero. Y, por supuesto, conocer diferentes patrones de diseño le permite crear la mejor solución para diferentes tipos de problemas.