DEV Community

Cover image for Patrón Observer (parte 1)
leobar37
leobar37

Posted on • Edited on

Patrón Observer (parte 1)

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 {}
}
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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
      }`
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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 {

  }
}
Enter fullscreen mode Exit fullscreen mode

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 {}
}
Enter fullscreen mode Exit fullscreen mode

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 {}
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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

relation

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);
Enter fullscreen mode Exit fullscreen mode

Lo que hecho es instancia nuestro Observable, agregarle dos suscripciones y cada cierto tiempo notificar aleatoriamente la que se subió un video.

console

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:

Top comments (0)