Patrón Observer (parte 2)
Published: 2021-10-20 04:00:28

En la primera parte implementé el patrón Observer, pero ligado a una lógica especifica. En este artículo quiero plantearlo de dos maneras más pero sin ligarlos a una lógica de negocio  e incluyendo un poco de programación funcional.

### Ejemplo 1:

Primero vamos a implementar el patrón de Observer de una manera que nos permita hacer una emisión de cualquier dato y poder suscribirnos a él.

```tsx
type Unsuscribe = () => void;

type Listener<T> = (value: T) => void;

export interface ISubject<T> {
  suscribe(listener: Listener<T>): Unsuscribe;
}
```

Este sería el planteamiento, vamos  tenemos un `Unsuscribe` el cual va a ser una función que me permite dejar de escuchar los cambios del sujeto y el `Listener` el cual es la función que vamos a llamar para notificar los cambios y finalmente `ISubject` .

```tsx
export class Subject<T> implements ISubject<T> {
  
 listeners = new Set<Listener<T>>();
 
	 next(value: T): void {
    this.listeners.forEach(listener => listener(value));
  }
  suscribe(listener: Listener<T>): Unsuscribe {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  }
}
```

Esta sería la implementación del patrón completa, esta clase tiene como finalidad emitir a todos los oyentes los valores que se vayan emitiendo, como un flujo de datos. Veámoslo en acción.

```tsx
const obs = new Subject<string>();

obs.next('hello');

obs.suscribe(d => {
  console.log('suscriber 1');
  console.log(d);
});

obs.next('World');

obs.suscribe(d => {
  console.log('suscriber 2');
  console.log(d);
});

obs.next('bye');

```

**Output:**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2zm35q2mbn0rph83s13y.png)


Para explicarlo un poco mejor lo voy a poner gráficamente:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5xtb7a243tsos6dg3plo.png)

La palabra hello no fue mostrada porque al momento que se emitió ese dato no había ningún listener escuchando, cuando se emitió world solo había uno y cuando apareció bye habían dos.

### Ejemplo 2:

Cuando tratamos con el dom a menudo vemos codigo así:

```tsx
document.addEventListener('click', () => {
  console.log('Hi, you click me :)');
});
```

Lo que indica que cuando el evento `click` ocurra esa función va a ser accionada. Y eso puede ocurrir en distintos lugares(puedes escuchar el evento `click` muchas veces). Ahora vamos a emplear el patrón observer de una manera diferente, ahora ya no tendremos un solo conjunto de **Listeners.** En ****lugar de eso vamos a tener un conjunto por cada evento.


```tsx
type Listener<T> = (value: T) => void;
type Listeners<T> = Array<Listener<T>>;
interface IEventEmmiter<T> {
  on(event: string, listener: Listener<T>): void;
  once(event: string, listener: Listener<T>): void;
  removeEventListeners(): void;
  emit(event: string, payload: T): void;
}

```

Está seria la estructura, los métodos dentro de `IEventEmmiter` tienen que cumplir con lo siguiente.

**on:**

Me permite suscribirme a un evento específico.

**once:**

Me permite suscribirme a un evento específico pero solo una vez.

**removeEventListeners:**

Remueve todos los oyentes,

**emit:** 

Emite datos según el evento.

Veamos como implementamos esto 😌.

lo primero que voy hacer plantear el almacenamiento de los Oyentes, para eso voy a usar `Map`.

```tsx
class EventEmmiter<T> implements IEventEmmiter<T> {
  private listenersManager = new Map<string, Listeners<T>>();

  constructor() {}

  emit(event: string, payload: T): void {}
  on(event: string, listener: Listener<T>): void {}
  once(event: string, listener: Listener<T>): void {}
  removeEventListeners(): void {}
}
```

Una vez planteado esto  podemos empezar a agregar código a  los métodos.

Empecemos con `on:`

```tsx
class EventEmmiter<T> implements IEventEmmiter<T> {
  private listenersManager = new Map<string, Listeners<T>>();

  constructor() {}
  emit(event: string, payload: T): void {}
  private getListeners(event: string) {
    return this.listenersManager.get(event) || [];
  }
  on(event: string, listener: Listener<T>): void {
    const listeners = this.getListeners(event);
    listeners.push(listener);
    this.listenersManager.set(event, listeners);
  }
  once(event: string, listener: Listener<T>): void {}
  removeEventListeners(): void {}
}
```

Lo único que vamos a hacer en `on` es agregar es agregar el `listener` al evento que le corresponda.

Ahora veamos `once`:

```tsx
class EventEmmiter<T> implements IEventEmmiter<T> {
  private listenersManager = new Map<string, Listeners<T>>();
  private onlyOnce = new Set<Listener<T>>();
  constructor() {}
  emit(event: string, payload: T): void {}
  private getListeners(event: string) {
    return this.listenersManager.get(event) || [];
  }
  on(event: string, listener: Listener<T>): void {
    const listeners = this.getListeners(event);
    listeners.push(listener);
    this.listenersManager.set(event, listeners);
  }
  once(event: string, listener: Listener<T>): void {
    this.onlyOnce.add(listener);
    this.on(event, listener);
  }

  removeEventListeners(): void {}
}
```

Para `once` he creado un  segundo almacenamiento  donde guardaré este tipo de listeners. Quizá una forma mucho mejor de hacerlo sea envolver a al listener dentro de un objeto para no tener dos listas, pero para no complicar las cosas lo voy a dejar así.

```tsx
class EventEmmiter<T> implements IEventEmmiter<T> {
  private listenersManager = new Map<string, Listeners<T>>();
  private onlyOnce = new Set<Listener<T>>();
  constructor() {}
  emit(event: string, payload: T): void {
    this.getListeners(event).forEach(listener => {
      if (this.onlyOnce.has(listener)) {
        this.onlyOnce.delete(listener);
        this.removeListener(event, listener);
      }
      listener(payload);
    });
  }
  private removeListener(event: string, listener: Listener<T>) {
    const newListeners = this.getListeners(event).filter(d => d !== listener);
    this.listenersManager.set(event, newListeners);
  }
  private getListeners(event: string) {
    return this.listenersManager.get(event) || [];
  }
  // rest of code ..

  removeEventListeners(): void {}
}
```

Bien a la hora de emitir el evento es donde es útil la segunda lista , si el listener se encuentra dentro dentro de `onlyOnce` este se tiene que eliminar.

Ahora solo faltaría el ultimo método `removeListeners`.

```tsx
class EventEmmiter<T> implements IEventEmmiter<T> {
  private listenersManager = new Map<string, Listeners<T>>();
  private onlyOnce = new Set<Listener<T>>();
  constructor() {}
  private getListeners(event: string) {
    return this.listenersManager.get(event) || [];
  }
  private removeListener(event: string, listener: Listener<T>) {
    const newListeners = this.getListeners(event).filter(d => d !== listener);
    this.listenersManager.set(event, newListeners);
  }
  emit(event: string, payload: T): void {
    this.getListeners(event).forEach(listener => {
      if (this.onlyOnce.has(listener)) {
        this.onlyOnce.delete(listener);
        this.removeListener(event, listener);
      }
      listener(payload);
    });
  }
  on(event: string, listener: Listener<T>): void {
    const listeners = this.getListeners(event);
    listeners.push(listener);
    this.listenersManager.set(event, listeners);
  }
  once(event: string, listener: Listener<T>): void {
    this.onlyOnce.add(listener);
    this.on(event, listener);
  }
  removeEventListeners(): void {
    this.onlyOnce = new Set();
    this.listenersManager = new Map();
  }
}
```

Listo una clase hermosa para manejar eventos, ahora en acción.

```tsx
const emmiter = new EventEmmiter<string>();

emmiter.on('hello', name => {
  console.log('sub 1');
  console.log('Hello ' + name);
});

emmiter.once('hello', name => {
  console.log('sub  4 (once)');
  console.log('Hello ' + name);
});
let cont = 0;
setInterval(() => {
  emmiter.emit('hello', 'Leobar' + cont);
  if (cont == 5) {
    emmiter.removeEventListeners();
  }
  cont++;
}, 1000);
```

**output :**

![Output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ek59b42eeztx70t2xd5u.gif)

Bueno ha pasado la prueba 😌.

Bien puedo decir que fue suficiente como para entender la utilidad de este patrón por cierto los dos ejemplos estan inspirado la libreria [rxjs](https://rxjs.dev/) y [EventEmiter](https://nodejs.org/api/events.html) de node js respectivamente.

[codigo completo](https://stackblitz.com/edit/typescript-lekhux?)