style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">

Observer pattern

Uno de los mas importantes patterns de diseno es Observer, este patron lo podemos ver en librerias como Reactive Extensions, React, Redux, Angular y varias librerias relacionadas con streaming, pero a veces no queremos toda una libreria porque nuestro requerimiento no es tan complejo, y escribir un simple Observer no es tan complicado, al menos no en Kotlin.

El modo de funcionar es bastante simple, tenemos un distribuidor de eventos y varios listeners cada vez que hay un evento, el distribuidor se encarga de enviar el evento a todos los que esten suscritos, es como crear un mini service bus.

https://en.wikipedia.org/wiki/Observer_pattern aqui hay una explicacion mucho mas detallada

Basicamente necesitamos 3 cosas, los suscriptores, una lista de suscritos y una forma de recibir y propagar eventos, vamos a implementar esto en kotlin;

El codigo

Para la lista de suscripciones solo creamos una lista de funciones, cada vez que alguien quiera escuchar agrega una funcion que recibira el modulo que hizo el evento, el tipo de evento y los datos relacionados. Finalmente tenemos el emisor, que propaga los eventos a todas las funciones de la lista. Como adicional creamos un cancelador de suscripcion, por si queremos dejar de recibir eventos.

class basicObserver {
    private var handlers: List<(module: String, event: String, data: HashMap<String, Any>) -> Any> = emptyList()

    fun subscribe(subscriber: (module: String, event: String, data: HashMap<String, Any>) -> Any) {
        handlers = handlers + subscriber
    }

    fun unsubscribe(subscriber: (module: String, event: String, data: HashMap<String, Any>) -> Any) {
        handlers = handlers.filter {
            it != subscriber
        }
    }

    fun emit(module: String, event: String, data: HashMap<String, Any>) {
        handlers.forEach {
            it.invoke(module, event, data)
        }
    }
}

Usando el Observer

Un pequeno ejemplo de como usar nuestro ejemplo:

fun main() {
    val obs = basicObserver()

    val subscriber = { module: String, event: String, data: HashMap<String, Any> ->
        println(module)
        println(event)
        println(data)
    }

    val subscriber2 = { module: String, event: String, data: HashMap<String, Any> ->
        println("dos $module")
        println("tres $event")
        println(data)
    }

    obs.subscribe(subscriber)

    obs.subscribe(subscriber2)

    obs.emit("hello", "world", hashMapOf("java" to "mexico"))

    println("")

    obs.unsubscribe(subscriber)

    obs.emit("new", "world", hashMapOf("java" to "mexico"))
}

creamos dos Suscribers, nos suscribimos al observer y luego emitimos modulo "hello" . evento "world" y un hasmap con java: mexico, despues desuscribimos la primera suscripcion y emitimos el segundo evento con modulo "new" . evento "world" y un hasmap con java: mexicodos.

La salida de esto es:

hello
world
{java=mexico}
dos hello
tres world
{java=mexico}

dos new
tres world
{java=mexicodos}

Justo lo que estabamos esperando las dos salidas para el primer evento porque tenemos dos subscriptores y la segunda salida solo para el segundo suscriptor.

Cuando usar esto?

Los observers son importantisimos si quieres saber que algo pasa en tu codigo sin estar esperando a que suceda, si necesitas programacion asyncrhona sirve para avisar que algo ya paso y no bloquear el thread, para escuchar eventos, si usas GUI para escuchar cualquier evento de la pantalla, para leer un archivo y saber cuando este termino de ser procesado, con algunas mejoras puedes usarlos para coordinar threads, etc.

En javascript que todo es asynchrono, tambien ayuda mucho asi que aqui hay un ejemplo:

function basicObserver() {
  this.handlers = [];

  this.subscribe = fn => {
    this.handlers.push(fn);
  };

  this.unsubscribe = fn => {
    this.handlers = this.handlers.filter(item => item !== fn);
  };

  this.emit = (m, t, o, thisObj) => {
    const scope = thisObj;
    this.handlers.forEach(item => item.call(scope, m, t, o));
  };
}

const observer = new basicObserver();

const subscriber1 = (module, event, data) => {
  console.log(module);
  console.log(event);
  console.log(data);
};

const subscriber2 = (module, event, data) => {
  console.log(module + 'dos');
  console.log(event + 'dos');
  console.log(data);
};

observer.subscribe(subscriber1);

observer.subscribe(subscriber2);

observer.emit('hello', 'world', {
  java: 'mexico'
});

observer.unsubscribe(subscriber2);

observer.emit('new', 'world', {
  java: 'mexicodos'
});

la salida

hello
world
{ java: 'mexico' }
hellodos
worlddos
{ java: 'mexico' }
new
world
{ java: 'mexicodos' }

Podrias usar Custom events en javascript pero la desventaja es que no todos los browsers lo soportan y eventos simples no puedes enviarlos con informacion adicional

style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">