Entendiendo el patrón observador (Observer Pattern) en Javascript

Fecha: 2020-05-05 - Autor: Sebastian Gomez - Únete a la discusiónJavascriptDesarrollo Web

En este post aprenderemos como implementar el patrón observer en Javascript y en que situaciones lo podemos usar.

El patrón observador es uno de los patrones de diseño de software más usado en Javascript. De el se extienden importantes aplicaciones que pueden ayudar a definir mejores arquitecturas en aplicaciones web, por lo que su uso y estudio es altamente recomendado. En este post aprenderemos como implementarlo en Javascript y en que situaciones lo podemos usar.

En primer lugar el patrón observador se define como un patrón de comportamiento es decir que dentro del mundo de la programación orientada a objetos es un patrón responsable por la comunicación entre objetos.

En segundo lugar el patrón observador también puedes encontrarlo como el patrón publicador-subscriptor o modelo-patrón y nos da una idea básica de lo que hace. En términos simples este patrón permite la notificación a objetos (subscriptores u observador) al cambio de otro objeto (publicador).

Veamos entonces un pequeño diagrama con las responsabilidades de los involucrados en este patrón. En primer lugar el publicador debe notificar a los subscriptores o observadores que algo cambió, y en segundo lugar los subscriptores deben poder subscribirse o de suscribirse del publicador en cualquier momento:

¿Cómo podemos entonces implementarlo en Javascript?, empecemos implementando una clase llamada Publicador que contenga los métodos subscribe(), unsubscribe(), y notify().

class Publicador {
constructor() {
this.subscriptors = \[\];
}
subscribe(subscriptor) {
this.subscriptors.push(subscriptor);
}
unsubscribe(subscriptor) {
this.subscriptors = this.subscriptors.filter(
(item) => item !== subscriptor
);
}
notify(event) {
this.subscriptors.forEach((item) => {
item.call(this, event);
});
}
}

Como verás hemos manejado la lista de subscriptores con un array de Javascript como propiedad de la clase así es posible fácilmente subscribir y des-subscribir subscriptores. También la función notify itera sobre cada uno de los subscriptores y se encarga de invocarlos con el evento.

Ahora imaginemos que usaremos esta definición de Publicador para un periódico que regularmente publica nuevas ediciones.

const periodico = new Publicador();

Bien, pensemos un poco más acerca de los clientes. Estos van a necesitar saber cuando llegue una nueva versión del periódico. Inicialmente pensemos en que los clientes son funciones:

function Observer(edicion) {
console.log("LLegó una nueva edición con el nombre de: " + edicion);
}
periodico.subscribe(Observer);
periodico.subscribe(Observer);
periodico.notify("Nueva edicion");

Con esta definición anterior al ejecutarlo obtenemos algo así:

"LLegó una nueva edición con el nombre de: Nueva edicion"
"LLegó una nueva edición con el nombre de: Nueva edicion"

Si queremos ser conscientes de que cliente recibió que edición del periódico podemos retocar un poco las definiciones de la función notify y de la función observer:

class Publicador {
...
notify(event) {
let index = 0;
this.subscriptors.forEach( item => {
item.call(this, index, event);
index++;
});
}
...
}
...
function Observer(index, edicion) {
console.log("Al Observador #" +
index + " le llegó una nueva edición con el nombre de: " +
edicion);
}

De esta manera tenemos como output algo mucho mas entendible:

"Al Observador #0 le llegó una nueva edición con el nombre de: Nueva edicion";
"Al Observador #1 le llegó una nueva edición con el nombre de: Nueva edicion";

Sin embargo como un mejor acercamiento podríamos definir una clase para los clientes que nos permita crear instancias de ella y tener un control mas granular. También debemos definir un método únicamente diseñado para escuchar por nuevas decisiones del periódico.

class Observador {
constructor(id) {
this.id = id;
console.log("Se ha creado el subscriptor #: " + id);
}
buzon(edicion) {
console.log(
"Subscriptor # " + this.id + " recibió una nueva edición: " + edicion
);
}
}
const subscriptor1 = new Subscriptor(1);
const subscriptor2 = new Subscriptor(2);
const subscriptor3 = new Subscriptor(3);

De esta forma podemos tener más control sobre los subscriptores y podemos subscribirlos y des-subscribirlos de mejor manera. A continuación verás como publicar multiples ediciones de un perioido así como la habilidad suscribir y des-suscribir clientes:

periodico.subscribe(subscriptor1);
periodico.subscribe(subscriptor2);
periodico.notify("Nueva edicion");
periodico.subscribe(subscriptor3);
periodico.notify("Segunda edicion");
periodico.unsubscribe(subscriptor1);
periodico.notify("Tercera edicion");
periodico.subscribe(subscriptor2);
periodico.notify("Nueva edicion");
periodico.subscribe(subscriptor3);
periodico.notify("Segunda edicion");
periodico.unsubscribe(subscriptor1);
periodico.notify("Tercera edicion");

Y obtenemos la siguiente salida:

"--- Primera edición ---";
"Subscriptor # 1 recibió una nueva edición: Nueva edicion";
"Subscriptor # 2 recibió una nueva edición: Nueva edicion";
"--- Segunda edición ---";
"Subscriptor # 1 recibió una nueva edición: Segunda edicion";
"Subscriptor # 2 recibió una nueva edición: Segunda edicion";
"Subscriptor # 3 recibió una nueva edición: Segunda edicion";
"--- Tercera edición ---";
"Subscriptor # 2 recibió una nueva edición: Tercera edicion";
"Subscriptor # 3 recibió una nueva edición: Tercera edicion";

De esta manera queda totalmente completo el patrón observer. Como vez es muy fácil implementar el patrón observer y su utilidad es casi inmediata. A continuación podrás observar todo el código completo de este ejemplo:

class Publicador {
constructor() {
this.subscriptors = \[\];
}
subscribe(subscriptor) {
this.subscriptors.push(subscriptor);
}
unsubscribe(subscriptor) {
this.subscriptors = this.subscriptors.filter(
(item) => item !== subscriptor
);
}
notify(event) {
this.subscriptors.forEach((item) => {
item.buzon.call(item, event);
});
}
}
class Subscriptor {
constructor(id) {
this.id = id;
console.log("Se ha creado el subscriptor #: " + id);
}
buzon(edicion) {
console.log(
"Subscriptor # " + this.id + " recibió una nueva edición: " + edicion
);
}
}
const periodico = new Publicador();
const subscriptor1 = new Subscriptor(1);
const subscriptor2 = new Subscriptor(2);
const subscriptor3 = new Subscriptor(3);
console.log("--- Primera edición ---");
periodico.subscribe(subscriptor1);
periodico.subscribe(subscriptor2);
periodico.notify("Nueva edicion");
console.log("--- Segunda edición ---");
periodico.subscribe(subscriptor3);
periodico.notify("Segunda edicion");
console.log("--- Tercera edición ---");
periodico.unsubscribe(subscriptor1);
periodico.notify("Tercera edicion");

Eso es todo, espero que este post te sea de utilidad y lo puedas aplicar a algún proyecto que tengas en mente y que simplemente te haya ayudado a entender la naturaleza del patrón observer. déjame un comentario si lograste implementarlo, si quieres añadir alguna otra funcionalidad o si tienes alguna duda no dudes en dejarme un comentario en la parte de abajo, recuerda que si te gustó también puedes compartir usando los links a las redes sociales en la parte de abajo.