Estamos trabajando en algo propio llamado KODOTI. Click para unirte
Se viene KODOTI. Únete

Patrón Observador con Javascript (Observer Pattern)

Mediante un ejemplo vamos a entender como funciona e implementa el patrón Observador en javascript.

Rodríguez Patiño, Eduardo
6,538 lecturas
Rodríguez Patiño, Eduardo
Hemos migrado hace poco nuestras publicaciones del blog antiguo. Si crees que esta se encuentra incompleta o sin coherencia deja un comentario para restaurarla manualmente.

Muy bien, hoy vamos el patrón Observer en javascript el cual nos va a permitir conocer el estado actual de un objeto ante un cambio. ¿wHATT? .. tranquilo, sigue leyendo.

¿En qué consiste el patrón?

Permite observar los cambios que tenga un objeto (según especifiquemos). Por ejemplo, supongamos que tenemos una aplicación en javascript que permite dar un examen en línea y queremos registrar cada respuesta que el usuario vaya seleccionado. Mediante este patrón vamos a inyectar el comportamiento que queremos que tenga el objeto para este evento determinado y pueda mandar una petición AJAX al servidor para registrar las respuesta que se ha seleccionado.

Este patrón esta compuesto por un Sujeto y un Observador, el sujeto será la persona/cosa encargada de realizar una determinada acción y el observador será el encargado de vigilar lo que el sujeto haga.

Ejemplo práctico

Muy bien he hecho un ejemplo práctico en la que vamos a interceptar cuando 2 pokemons peleen y uno de ellos ganen, jaaaaa seguro esperabas el "ejemplo del examen online". Pues no, vamos hacer un ejemplo más práctico para poder comprender la lógica.

Observador

Lo primero que haremos es crear una clase llamada Observable

function Observable() {
    this.observers = [];

    this.add = function (observer) {
        this.observers.push(observer);
    };

    this.notify = function (obj) {
        this.observers.forEach(function (observer) {
            observer.call(null, obj);  
        });
    };
}

Esta clase nos va a permitir reutilizar código y dejar a un lado la odiosa tarea de copiar/pegar.

Sujeto

Luego tenemos nuestro sujeto que será el encargado de entablar combante entre 2 pokemons y determinar quien gano.

function PokemonCombate(pokemon1, pokemon2){
    this.observable = new Observable();

    this.pokemon1 = pokemon1;
    this.pokemon2 = pokemon2;
    this.ronda = 1;
}

Mediante prototype vamos a extender sus funcionalidades agregando la lógica del combate. Asimismo, este método tendrá la responsabilida de decirle a nuestro observador que ha habido un cambio de estado o en este caso un ganador en el combate.

PokemonCombate.prototype.combatir = function () {
  var ganador,
      perdedor;

  if(Math.floor((Math.random() * 2) + 1) === 1) {
      ganador = this.pokemon1;
      perdedor = this.pokemon2;
  } else {
      ganador = this.pokemon2;
      perdedor = this.pokemon1;
  }

  this.observable.notify({
    ganador: ganador,
    perdedor: perdedor,
    ronda: this.ronda
  });

  this.ronda++;
};

La lógica es muy simple, si el valor aleatorio arroja 1 pues gana el primer pokemon del caso contrario el otro.

Poniendo en práctica el código

Ahora debemos especificar el comportamiento que tenga nuestro Observador respecto al Sujeto.

var combate = new PokemonCombate('Pikachu', 'Meowth');

combate.observable.add(function(obj){
    console.log('El ganador de la ronda ' + obj.ronda + ' es: ' + obj.ganador)
});

Podemos agregar varios Observadores a nuestro Sujeto, supongamos que queremos mostrar tambíen quien perdió.

combate.observable.add(function(obj){
    console.log('El perdedor de la ronda ' + obj.ronda + ' es: ' + obj.perdedor)
});

¿Y no era más facil hacer esto?

PokemonCombate.prototype.combatir = function () {
  var ganador,
      perdedor;

  if(Math.floor((Math.random() * 2) + 1) === 1) {
      ganador = this.pokemon1;
      perdedor = this.pokemon2;
  } else {
      ganador = this.pokemon2;
      perdedor = this.pokemon1;
  }

  this.observable.notify({
    ganador: ganador,
    perdedor: perdedor,
    ronda: this.ronda
  });

    console.log('El ganador de la ronda ' + obj.ronda + ' es: ' + obj.ganador);
    console.log('El perdedor de la ronda ' + obj.ronda + ' es: ' + obj.perdedor)

  this.ronda++;
};

¿Se dieron cuenta?, es decir poner en el método combatir el código de la notificación. Si sería más práctico, pero tenemos un problema que estamos dandole alto acoplamiento a nuestro código y si quisieramos agregar mayor funcionalidad pues tendríamos que modificar ese código, en cambio de la manera que les planteo es más práctico y escalable, ya que podemos inyectar la lógica (inyección de dependencia) fuera de la clase haciendole más fácil de mantener a largo plazo.

La razón porque no es mejor trabajarlo de esta manera

Veamos un ejemplo, su desarrollador jr si entrará a la clase tendría que validar en que momento preciso tendrá que agregar el código de la notificación y en muchos escenarios reales el código no va ser tan sencillo como lo he planteado. Entonces, el programador jr hizo mal la validación y manda la notificación bajo la condición incorrecta. Ahora, si el programador jr sabe que tiene que agregar la notificación mediante el patrón observer se le será más sencillo porque su código se resumen a esto.

combate.observable.add(function(obj){
    console.log('El ganador de la ronda ' + obj.ronda + ' es: ' + obj.ganador)
});

Ejemplo completo

<script>
function Observable() {
  this.observers = [];

  this.add = function (observer) {
    this.observers.push(observer);
  };

  this.notify = function (obj) {
    this.observers.forEach(function (observer) {
      observer.call(null, obj);  
    });
  };
}

function PokemonCombate(pokemon1, pokemon2){
  this.observable = new Observable();

  this.pokemon1 = pokemon1;
  this.pokemon2 = pokemon2;
  this.ronda = 1;
}

PokemonCombate.prototype.combatir = function () {
    var ganador,
        perdedor;

  if(Math.floor((Math.random() * 2) + 1) === 1) {
      ganador = this.pokemon1;
      perdedor = this.pokemon2;
  } else {
      ganador = this.pokemon2;
      perdedor = this.pokemon1;
  }

  this.observable.notify({
    ganador: ganador,
    perdedor: perdedor,
    ronda: this.ronda
  });

  this.ronda++;
};

window.onload = function(){
  var combate = new PokemonCombate('Pikachu', 'Meowth');

  combate.observable.add(function(obj){
    document.getElementById("ganador").innerHTML = ('El ganador de la ronda ' + obj.ronda + ' es: ' + obj.ganador);
  });

  combate.observable.add(function(obj){
    document.getElementById("perdedor").innerHTML = ('El perdedor de la ronda ' + obj.ronda + ' es: ' + obj.perdedor);
  });

  document.querySelector('#combate').addEventListener('click', function(){
    combate.combatir();
  })
}
</script>

<button id="combate">Combate!</button>
<div id="ganador"></div>
<div id="perdedor"></div>
¿Te gustó nuestra publicación?
Suscríbete a nuestro boletín