martes, 22 de mayo de 2018

Observer Pattern en Javascript

Observer Pattern con Javascript version 1

En esta primera versión el elemento Subject, se crea como función con un arreglo vacio de observers a la cual se le agregan a su prototipo funciones especificas del patrón que son subscribe(), unsubscribe(), notifyObserver(), notifyObservers().

  Se utiliza un objecto log que es de ayuda para desplegar los mensajes en la consola, este log objeto utiliza el module pattern de javascript, que aisla sus metodos de scope externo. lo cual no hace en esta version el objeto Subject.

 El Observer es una variable sencilla que se iguala a una función que usa el objeto log. 
El subject se crea con new Subject(), y los observers se crean como variables sencillas solo diferenciadas por su nombre propio y el mensaje estatico en el objeto log que utiliza. esto se mejora en la version 2.

function Subject() {
    this.observers = [];
}

Subject.prototype = {
   
    subscribe: function(fn){
        this.observers.push(fn);
    },
   
    unsubscribe: function(fn){
        this.observers = this.observers.filter(
            function(item) {
                if (item !== fn) {
                    return item;
                }
            }
        );
    },

    notifyObserver: function(id, msgEvent, thisObj) {
        var scope = thisObj || window;
        this.observers[ this.observers.indexOf(id) ].call(scope, msgEvent);
    },
   
    notifyObservers: function(msgEvent, thisObj){
        var scope = thisObj || window;
        this.observers.forEach(function(current){
            current.call(scope, msgEvent);
        });
    }
}

//log helper
var log = (function() {
    var log = "";
   
    return {
        add: function(msg) { log += msg + "\n"; },
        show: function() {console.log(log); log = "";}
    }
})();

var Observer = function(item) {
    log.add("observer notificado: " + item)
}

function run(){
    var observer1 = function(item) {
        log.add("observer1 notificado: " + item);
    };
    var observer2 = function(item) {
        log.add("observer2 notificado: " + item);
    };
    var observer3 = function(item) {
        log.add("observer3 notificado: " + item);
    };
    var observer4 = function(item) {
        log.add("observer4 notificado: " + item);
    };
   
    var subject = new Subject();

    subject.subscribe(observer1);
    subject.subscribe(observer2);
    subject.subscribe(observer3);
    subject.subscribe(observer4);
    subject.notifyObserver(observer1, 'Activate');
    subject.notifyObserver(observer2, 'Espera');
    subject.notifyObservers('Arranca');
    subject.unsubscribe(observer1);
    subject.notifyObservers('Detenete');
    subject.subscribe(observer1);
    subject.notifyObservers('Publica');
       
    log.show();
}


Despliegue en consola.

observer1 notificado: Activate
observer2 notificado: Espera
observer1 notificado: Arranca
observer2 notificado: Arranca
observer3 notificado: Arranca
observer4 notificado: Arranca
observer2 notificado: Detenete
observer3 notificado: Detenete
observer4 notificado: Detenete
observer2 notificado: Publica
observer3 notificado: Publica
observer4 notificado: Publica
observer1 notificado: Publica


Version 2, En este caso el objeto Subject se modifica para que cumpla con el module pattern de javascrip, sus métodos se aislan del entorno, solo disponibles al llamar Subject.metodo(), y el Observers tambien se le aplica el module pattern y al construirse con new, el ID del objeto se implime en el metodo de consola que despliega al momento del log. o sea no se usa una propiedad que se inicializa declarativamente, y se le agrega el método notify con un parametro de mensaje a notificar.
En este caso se crean los observadores con new y se inicializa su ID con el parametro de constructor "implicito".

//Observer Pattern
var Subject = function() {
    let observers = [];
   
    return {
        subscribeObserver: function(observer) {
            observers.push(observer);
        },
        unsubscribeObserver: function(observer) {
            var index = observers.indexOf(observer);
            if (index > -1) {
              observers.splice(index, 1);   
            }
        },
        notifyObserver: function(observer, msgEvent) {
            var index = observers.indexOf(observer);
            if (index > -1) {
                observers[index].notify( msgEvent);
            }
        },
        notifyAllObservers: function(msgEvent) {
        /* version for(..)
          for(var i = 0; i < observers.length; i++){
                observers[i].notify(msgEvent);
            };
        */
            observers.forEach(function(current){
                current.notify(msgEvent);
            });
        }
    };
};

var Observer = function(ID){
    return {
        notify: function(msgEvent){
            console.log("Observer " + ID + " se ha notificado con " + msgEvent+ ".");
        }
    }
}

function test(){
    var subject = new Subject();

    var observer1 = new Observer('UNO');
    var observer2 = new Observer(2);
    var observer3 = new Observer('TRES');
    var observer4 = new Observer(4);

    subject.subscribeObserver(observer1);
    subject.subscribeObserver(observer2);
    subject.subscribeObserver(observer3);
    subject.subscribeObserver(observer4);
   
    subject.notifyObserver(observer2);
    subject.unsubscribeObserver(observer2);
   
    subject.notifyObserver(observer2,'Activate!');
    subject.notifyObserver(observer3,'Activate!')

    subject.notifyAllObservers('Desactivate!');
};
   

El despliegue al correrlo es:

Observer 2 se ha notificado con undefined.
observerV2.js:37 Observer TRES se ha notificado con Activate!.
observerV2.js:37 Observer UNO se ha notificado con Desactivate!.
observerV2.js:37 Observer TRES se ha notificado con Desactivate!.
observerV2.js:37 Observer 4 se ha notificado con Desactivate!.


Versión de Addy Osmany

function ObserverList() {
    this.observerList = [];
}

ObserverList.prototype.add = function( obj ){
    return this.observerList.push( obj );
};

ObserverList.prototype.count = function() {
    return this.observerList.length;
};

ObserverList.prototype.get = function( index ) {
    if ( index > -1 && index < this.observerList.length ) {
        return this.observerList[ index ];
    }
};

ObserverList.prototype.indexOf = function( obj, startIndex ) {
    var i = startIndex;
   
    while( i < this.observerList.length ){
        if (this.observerList[i] === obj){
            return i;
        }
        i++;
    }
   
    return -1;
};

ObserverList.prototype.removeAt = function( index ) {
    this.observerList.splice( index , 1);
};

function Subject() {
    this.observers = new ObserverList();
}

Subject.prototype.addObserver = function( observer ){
    this.observers.add( observer );
}

Subject.prototype.removeObserver = function( observer ){
    this.observer.removeAt( this.observers.indexOf( observer, 0));
}

Subject.prototype.notify = function( context ) {
    var observerCount = this.observers.count();
    for (var i=0; i < observerCount; i++) {
        this.observers.get(i).update( context );
    }
};

//El Observer
// al implementar el observador se implementara el update
function Observer() {
    this.update = function() {
        //codigo que se sobreescribira ...
    };
}


El Html de prueba, que crea checkboxes según de optima el boton de crear checkboxes y otro checkbox que envia mensajes a todos los checkboxes creados para que cambien su valor.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Observer Pattern (Addy Osmani)</title>
    <script src="js/observerAO.js"></script>
</head>
<body>
    <button id="addNewObserver">Add New Observer checkbox</button>
    Notificar a Observers <input id="mainCheckbox" type="checkbox"/>
    <div id="observersContainer"></div>
    <script>
        //Extiende un objeto con una extension
        function extend( obj, extension ) {
            for( var key in extension) {
                obj[key] = extension[key];
            }
        }
       
        //Referencias a nuestros elementos DOM
       
        var controlCheckbox = document.getElementById( "mainCheckbox" ),
                addBtn = document.getElementById( "addNewObserver" ),
                container = document.getElementById( "observersContainer" );
       
        // Concrete Subject
       
        // Extend le checkbox de control con la clase Subject
        extend( controlCheckbox, new Subject() );
       
        //Clicking el checkbox dispararar notificaciones a sus observers
        controlCheckbox.onclick = function() {
            controlCheckbox.notify( controlCheckbox.checked );
        };
       
        addBtn.onclick = addNewObserver;
       
        // Concrete Observer
       
        function addNewObserver() {
          // crear un nuevo checkbox para aggregarlo
            var check = document.createElement( "input" );
            check.type = "checkbox";
           
            // Extiende el checkbox con la clase Observer
            extend( check, new Observer() );
           
            // Sobreescribe update con comportamiento personalizado
            check.update = function( value ){
                this.checked = value;
            };
           
            // Agrega el nuevo observer a la lista de observers
            // para nuestro subject principal
            controlCheckbox.addObserver( check );
           
            // append el item al contenedor
            container.appendChild( check );
        }
    </script>
</body>
</html>


fin texto.

No hay comentarios:

Publicar un comentario