Introducción
Contenido
En este artículo, implementaremos el patrón de diseño del observador para resolver un problema común en el desarrollo de software orientado a objetos.
Los patrones de diseño son soluciones estandarizadas para problemas comunes en la industria del desarrollo de software. Al estar familiarizado con ellos, un desarrollador puede reconocer dónde debería implementarse cada uno y cómo ayudaría a resolver un problema de diseño en particular.
La prevención de desastres de diseño temprano puede ahorrar una gran cantidad de tiempo y costo para un equipo que intenta lanzar un producto.
Patrones de diseño de comportamiento
Los patrones de diseño de comportamiento proporcionan asignación de responsabilidad entre instancias de clases. Además, definen tipos de relaciones y comunicación entre objetos.
La idea principal es lograr un comportamiento esperado de una aplicación y crear un diseño flexible al mismo tiempo.
Patrón de diseño del observador
El patrón de diseño del observador es una forma de diseñar un subsistema que permite que muchos objetos respondan automáticamente a los cambios de un objeto en particular que está siendo «observado».
Te puede interesar:Concurrencia en Java: la palabra clave volátilAborda la descomposición de un Observable
y Observer
s – o un editor y suscriptores.
Por un Observable
objeto, usamos el término Sujeto. Los objetos que están suscritos a los cambios del Sujeto se denominan Observadores. Un sujeto y los observadores suelen depender de uno a varios.
El patrón de diseño del observador también se conoce como patrón de evento-suscriptor o de escucha.
Nota: Java tiene una implementación oficial del Observer Design Pattern y es la columna vertebral de JMS (Java Message Service). Generalmente se usa para crear aplicaciones impulsadas por pares, sin embargo, la implementación oficial no está muy extendida y muchas personas implementan el patrón de acuerdo con sus propios casos de uso.
Motivación
Probablemente, el ejemplo más conocido es un oyente de botones que realiza una acción al hacer clic en el botón. Este patrón, en general, es bastante común en los componentes de la GUI de Java. Es una forma de reaccionar ante eventos que suceden con objetos visuales.
Como usuario de las redes sociales, es posible que esté siguiendo a algunas personas. Podemos decir que eres un observador de las redes sociales de tu amigo (sujeto de observación) y recibes notificaciones sobre sus nuevas publicaciones y eventos de la vida. Curiosamente, tu amigo también es un observador de tu feed.
Te puede interesar:¿Java «pasa por referencia» o «pasa por valor»?Agreguemos más complejidad y digamos que probablemente tenga varios o incluso cientos de observadores diferentes y ellos pueden reaccionar de manera diferente a sus publicaciones. Es posible que un objeto pueda ser sujeto de observación y observador de otro sujeto. Incluso pueden tener esta relación entre ellos.
Como ejemplo más real: una alarma contra incendios en un centro comercial debe notificar a todas las tiendas que se está produciendo un incendio. Estas tiendas están observando la señal de alarma contra incendios y reaccionando a sus cambios.
Como puede ver, el problema está bastante extendido y muchas veces no es trivial resolverlo con otros diseños.
Implementación
Supongamos que una cadena de tiendas desea notificar a sus clientes leales sobre una venta en curso. El sistema enviaría un mensaje corto a todos los clientes suscritos cada vez que se activa una venta.
En este caso, nuestra tienda es objeto de observación y nuestros clientes la están observando. Definamos el Subject
y Observer
interfaces para que implementen nuestros objetos:
public interface Subject {
public void addSubscriber(Observer observer);
public void removeSubscriber(Observer observer);
public void notifySubscribers();
}
los Subject
La interfaz es bastante sencilla. Proporciona métodos para agregar y eliminar suscriptores / observadores y notificarles un cambio.
los Observer
la interfaz es aún más simple:
public interface Observer {
public void update(String message);
}
Lo único que un Observer
lo que realmente necesita es saber cuándo hay una actualización de su tema. Su comportamiento basado en esta actualización diferirá entre clases.
Con nuestras interfaces fuera del camino, implementemos el Subject
interfaz a través de una tienda:
public class Store implements Subject {
private List<Observer> customers = new ArrayList<>();
@Override
public void addSubscriber(Observer customer) {
customers.add(customer);
}
@Override
public void removeSubscriber(Observer customer) {
customers.remove(customer);
}
@Override
public void notifySubscribers() {
System.out.println("A new item is on sale! Act fast before it sells out!");
for(Observer customer: customers) {
customer.update("Sale!");
}
}
}
La tienda contiene una lista de observadores (clientes) e implementa los métodos para la adición y eliminación de clientes de la lista.
los notifySubscribers()
El método simplemente recorre la lista de ellos y les envía una actualización.
Podemos tener tantos Observer
implementaciones como nos gustaría. Es natural que las personas reaccionen de manera diferente a una venta. Un adicto a las compras probablemente saltará de alegría, mientras que un cliente pasivo probablemente tomará nota de la venta y la recordará para más adelante.
Avancemos e implementemos estos dos tipos de clientes:
public class ShopaholicCustomer implements Observer {
@Override
public void update(String message) {
processMessage(message);
}
private void processMessage(String message) {
System.out.println("Shopaholic customer is interested in buying the product on sale!");
// A complex psychologic response to a sale by a shopaholic
}
}
public class PassiveCustomer implements Observer {
@Override
public void update(String message) {
System.out.println("Passive customer made note of the sale.");
// Passive customer does not react to the message too much
}
}
Y finalmente, echemos un vistazo al patrón de diseño del observador en acción al activar una venta en una tienda que está siendo vista por algunos clientes:
public static void main(String[] args) {
// Initialization
Subject fashionChainStores = new ChainStores();
Observer customer1 = new PassiveCustomer();
Observer customer2 = new ShopaholicCustomer();
Observer customer3 = new ShopaholicCustomer();
// Adding two customers to the newsletter
fashionChainStores.addSubscriber(customer1);
fashionChainStores.addSubscriber(customer2);
// Notifying customers (observers)
fashionChainStores.notifySubscribers();
// A customer has decided not to continue following the newsletter
fashionChainStores.removeSubscriber(customer1);
// customer2 told customer3 that a sale is going on
fashionChainStores.addSubscriber(customer3);
// Notifying the updated list of customers
fashionChainStores.notifySubscribers();
}
Y ejecutar este fragmento de código producirá:
A new item is on sale! Act fast before it sells out!
Passive customer made note of the sale.
Shopaholic customer is interested in buying the product on sale!
A new item is on sale! Act fast before it sells out!
Shopaholic customer is interested in buying the product on sale!
Shopaholic customer is interested in buying the product on sale!
Cambiar el estado de la tienda da como resultado el cambio de estado de los clientes suscritos. Este mismo principio se aplicaría a una alarma de incendio o un servicio de noticias. Tan pronto como alguien publica una publicación, todos los observadores son notificados y toman alguna acción dependiendo de su responsabilidad / interés.
Podemos modificar la lista de observadores de un sujeto en cualquier momento. Además, podemos agregar cualquier implementación del Observer
interfaz. Esto nos da la capacidad de construir un sistema robusto impulsado por eventos que envía actualizaciones a los observadores y actualiza todo el sistema en función de los cambios en un solo objeto en particular.
Pros y contras
El patrón de diseño del observador es una gran contribución al apoyo del principio de diseño de apertura / cierre. Nos ayuda a construir diseños con alta cohesión pero un acoplamiento flojo.
Te puede interesar:Nube de Spring: HystrixEn otras palabras, el Observador y el Sujeto tienen una misión estrictamente especificada. El sujeto actualiza a un observador con cierta información y no conoce la implementación del observador. Esta característica nos da flexibilidad.
Este patrón nos permite agregar y eliminar observadores en cualquier momento. No es necesario que modifique el sujeto ni el observador.
Sin embargo, hay un problema en el patrón de diseño del observador.
El orden de las notificaciones no está bajo nuestro control. No hay prioridad entre los suscriptores durante la notificación.
Esto significa que si la ejecución de un Observer depende de la ejecución de otro Observador de antemano, no hay garantía de que estos dos se ejecuten en ese orden.
Sin embargo, es valioso comprender que un patrón es una descripción de alto nivel de una solución en particular. Cuando aplicamos un patrón a dos aplicaciones diferentes, nuestro código será diferente. Podemos ordenar a nuestros observadores y recibirán una notificación en el orden esperado. Esta no es una característica del patrón de diseño del observador, pero es algo que podemos hacer.
Te puede interesar:Tutorial de Spring ReactorConclusión
Cuando conoce los patrones de diseño, algunos problemas complejos se pueden reducir a soluciones simples probadas.
El patrón de diseño del observador es realmente útil en sistemas controlados por eventos donde muchos objetos pueden depender del estado de otro objeto. Si se implementa mal, esto dará como resultado una aplicación rígida y acoplada donde es realmente difícil probar los objetos individualmente y actualizar el código será una molestia.
En este artículo, exploramos cómo podemos resolver este problema y crear una solución flexible y desacoplada que su equipo agradecerá.