Patrón Observer (parte 1)
leobar37
Posted on October 18, 2021
Patrón observer uno de los patrones que hace que parezcas un mago con el código, seria como:
Mira voy a ingresar un dato aquí y va a aparecer algo allá 😌
pero bueno, si haz escuchado sobre rxjs y programación reactiva Pero bueno, si has escuchado sobre rxjs y programación reactiva supongo que ya sabes un poco sobre esto, pero si has utilizado la librería sin conocer el patrón, ven y peleamos, vale no 😁, de hecho yo también lo hice y pues tan mal no me fue. Aun así creo que aprender el patrón he implementarlo por ti mismo hace que todo sea un poco más fácil y abre más posibilidades.
Por cierto todo lo siguiente, implementación y explicación es mi manera de aprenderlo es más te aconsejo leer primero este maravilloso artículo.
¿Que es el patrón observer?
Es un patrón de comportamiento que define un mecanismo de suscripción para notificar a una serie de objetos sobre cualquier cambio que le suceda al objeto que están observando.
Lo primero que se nos puede venir a la mente aquí, puede ser YouTube y las subscripciones, las notificaciones de tu red social favorita, etc. Con estos ejemplos podemos analizar el hecho de que el patrón observer le da la posibilidad de elegir que tipo de notificaciones recibir al sujeto, por ejemplo en YouTube cada vez que me suscribo a un canal voy a estar recibiendo notificaciones de cuando subió un vídeo este canal en particular.
Implementación:
Bien juguemos un poco con la lógica de suscripcion en YouTube.
EL patrón Observer consta de un notificador que a la vez es el sujeto el cual contiene el estado que es notificado a las respectivas suscripciones. En este caso nuestro sujeto será YouTube.
class Youtube {
constructor() {}
// notifies you when a video has been uploaded
notify(notify: any): void {}
// register a suscription
suscribe(sub: any): void {}
unsuscribe(sub: any): void {}
}
Aquí tenemos a nuestro Sujeto con los métodos necesarios para manejar las suscripciones, pero estos reciben parámetros tipo any
. Arreglemos eso:
interface Subscriber<T> {
update(event: T): void;
}
export type Notification = {
nameVideo: string;
channel: string;
date?: Date;
};
// suscription parameters
type SubscriptionInfo = {
id: string;
channel: string;
};
La parte interesante aquí es Subscriber
que es la interfaz que nos va a ayudar a definir el método que se llama cuando ocurre un evento en este caso cuando se sube un video al canal.
Ahora implementemos esa suscripción.
class YoutubeSubscription implements Subscriber<Notification> {
private sub: SubscriptionInfo;
constructor(sub: SubscriptionInfo) {
this.sub = sub;
}
getSub() {
return this.sub;
}
// this method is called when the subject wants to notify and event
update(event: Notification): void {
console.log(
` (${event.date.toISOString()}) ${event.channel} uploaded a new video : ${
event.nameVideo
}`
);
}
}
Ahora ya tenemos la suscripción y el cuerpo del evento en este caso es Notification
y la implementación del método update
.
Ahora veamos el cambio en la clase Youtube
class Youtube {
constructor() {}
notify(notify: Notification): void {}
suscribe(sub: YoutubeSubscription): void {}
unsuscribe(sub: YoutubeSubscription): void {
}
}
Listo ahora ya no tenemos any
y tenemos implementado nuestro sujeto el cual ahora llamaremos Observable.
Anteriormente creamos un método createDatabase
que fabricaba un objeto Database
con algunos métodos útiles para manejar nuestros datos puedes ver el código aquí. Entonces, vamos a usar este método para los canales.
interface Chanel extends BaseRecord {
name: string;
}
class Youtube {
channels = createDatabase<Chanel>({
typeId: 'incremental'
});
constructor() {
this.channels.insert({
name: 'leobar'
});
this.channels.insert({
name: 'nose'
});
}
notify(notify: Notification): void {}
suscribe(sub: YoutubeSubscription): void {}
unsuscribe(sub: YoutubeSubscription): void {}
}
Ahora tenemos los canales los cuales deberían de tener un conjunto de suscripciones, voy a manejar esa lógica por aparte manteniendo en memoria las suscripciones activas con un identificador, en este caso el nombre del canal.
class Youtube {
suscriptions: Map<string, YoutubeSubscription[]> = new Map();
channels = createDatabase<Chanel>({
typeId: 'incremental'
});
constructor() {
this.channels.insert({
name: 'leobar'
});
this.channels.insert({
name: 'nose'
});
}
notify(notify: Notification): void {}
suscribe(sub: YoutubeSubscription): void {}
unsuscribe(sub: YoutubeSubscription): void {}
}
En este caso estoy manejando las suscripciones con la estructura Map . Ahora agreguemos lógica a los métodos.
class Youtube {
// .. code
notify(notify: Notification): void {
this.suscriptions.get(notify.channel).forEach(d => d.update(notify));
}
get getChannels() {
return this.channels.findAll({});
}
suscribe(sub: YoutubeSubscription): void {
// if channel does not exist throw an exception
if (this.channels.findAll({ name: sub.getSub().channel }).length == 0) {
throw new Error('This channel does not exist');
}
let subs: YoutubeSubscription[] = [];
if (this.suscriptions.has(sub.getSub().channel)) {
subs = this.suscriptions.get(sub.getSub().channel);
}
subs.push(sub);
this.suscriptions.set(sub.getSub().channel, subs);
}
unsuscribe(sub: YoutubeSubscription): void {
let channelSubs = this.suscriptions.get(sub.getSub().channel);
if (channelSubs) {
channelSubs = channelSubs.filter(sub => sub !== sub);
this.suscriptions.set(sub.getSub().channel, channelSubs);
console.log(`${sub.getSub().id} Unsuscribed`);
console.log('Suscribers :' + channelSubs.length);
console.log(channelSubs);
}
}
}
Listo. Ahora tenemos implementando el patrón. Youtube
es nuestro Observable y YoutubeSubscription
lo vamos a llamar observer.Ahora nos damos cuenta que estamos tratando con una relación de uno a muchos
Ahora vamos a probar lo que hemos hecho:
const youtube = new Youtube();
const leobarSub = new YoutubeSubscription({
channel: 'leobar',
id: '1'
});
const noseSubscription = new YoutubeSubscription({
channel: 'nose',
id: '2'
});
youtube.suscribe(leobarSub);
youtube.suscribe(noseSubscription);
let cont = 0;
const channels = youtube.getChannels.map(d => d.name);
setInterval(() => {
const ramdom = Math.floor(Math.random() * channels.length);
youtube.notify({
channel: channels[ramdom],
nameVideo: 'video nro:' + cont,
date: new Date()
});
if (cont === 5) {
youtube.unsuscribe(leobarSub);
}
if (cont == 8) {
youtube.unsuscribe(noseSubscription);
}
cont++;
}, 2000);
Lo que hecho es instancia nuestro Observable, agregarle dos suscripciones y cada cierto tiempo notificar aleatoriamente la que se subió un video.
Hasta aquí hemos logrado implementar el patrón Observable de una manera que veamos cuál es su sentido de existir y su utilidad. Dejaré esta parte hasta aquí, En segunda parte quiero hacer una implementación más generalizada y ver como este patrón nos abre las puertas hacia la programación reactiva 😌.
Código completo aquí:
Links
Si todavía tienes dudas sobre como funciona este patrón puedes consultar los siguientes links:
Posted on October 18, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 26, 2024