El patrón Javascript Proxy y los contextos paralelos

13 Abr 2011

Introducción

Continuamos viendo patrones de diseño. Hemos analizado por el momento los dos más conocidos: el patrón módulo y el módulo revelado. Toca ahora el turno de un recién llegado, el intraducible This Namespace Proxy.

Creado por el desarrollador inglés James Edwards, este patrón utiliza los comandos apply y call para crear dos capas en las que contexto y argumentos se separan. Resulta, de lejos, el diseño más elegante visto hasta ahora:

El código

var myApp = {};
(function(){
  // Private variables / properties
  var foo = 'Hello';
  var bar = 'World';
 
  //Private method
  var myMessage = function(){
    return foo + ' ' + bar;
  };
 
  // Public Method
  this.sum = function( param1, param2 ){
    return param1 + param2;
  };
 
}).apply( myApp );
 
console.log( myApp.sum( 10, 5 ) ); // 15
console.log( myApp.myMessage() ); // Error, myApp.myMessage is not a function

La diferencia de este patrón con respecto a los anteriores está en la propia función que lo envuelve: en lugar de un autoejecutable a secas, estamos añadiendo los métodos al objeto principal mediante el comando apply.

En este contexto, this referencia siempre al padre (el objeto al que estamos extendiendo) lo que permite una flexibilidad extrema. La distinción entre métodos públicos y privados se realiza ahora mediante dicha referencia.

Una primera ventaja del uso de this, es que, al no tratarse de variables, estamos protegiendo el nombre de los métodos públicos de reasignaciones accidentales que puedan darse más adelante.

Pero además de añadir una capa de seguridad, este patrón permite avanzar en cuanto a escalabilidad de una arquitectura Javascript.

Módulos en paralelo

This Namespaces Proxy es brillante por su simplicidad conseguida gracias a un correcto uso del lenguaje: no hay artificios ni malabarismos como los que hemos visto anteriormente con el método return.

Tomándolo como punto de partida, podemos continuar jugando con el API apply y call para obtener un modelo capaz de ser asignado a dos, o más, contextos diferentes permitiéndo una ejecución en paralelo. Esta funcionalidad resulta extremadamente útil en aplicaciones complejas donde varios procesos participan de los mismos algoritmos sin interferir entre ellos. Un ejemplo básico de esta estructura sería:

// Contexts
var context1 = {}, context2 = {};
 
// Method for any context
var incrementNumber = function( param1 ){
  var startNumber = param1 || 0;
  this.increment = function(){
    return startNumber++;
  }
};
 
incrementNumber.call( context1 );
incrementNumber.call( context2, 10 );
 
console.log( context1.increment() ); // 0
console.log( context1.increment() ); // 1
console.log( context1.increment() ); // 2
console.log( context2.increment() ); // 10
console.log( context2.increment() ); // 11
console.log( context2.increment() ); // 12

Analizando el código anterior, vemos que partimos de dos contextos diferentes; en este caso, objetos vacíos. El método común que queremos aplicar se define más adelante y luego se asocia a los contextos anteriores mediante un call. El resultado son dos objetos independientes participando del mismo método.

Esta flexibilidad permite, por ejemplo, extender un único entorno consevando la herencia del método común que pasa a estar disponible mediante la referencia this:

context1.incrementTwice = function(){
  // First
  this.increment();
  // Second and go
  return this.increment();
};
 
console.log( context1.incrementTwice() ); // 1

El ejemplo anterior, aunque poco útil, muestra cómo se puede extender un contexto (objeto) de forma individual conservando la funcionalidad de los métodos comunes.

Exprimiendo el contexto

Angus Croll considera ir más allá: propone tomar una librería completa, contenerla dentro de un nuevo objeto y usar apply para asociarla al contexto que escojamos.

//library code
var protoQueryMooJo = function() {
    //everything
}
 
//user code
var thirdParty = {};
protoQueryMooJo.apply(thirdParty);

Esto permitiría por ejemplo, usar jQuery únicamente dentro de un contexto del código y Mootools en otro. Esto abre un amplio abanico de posibilidades en cuanto al ensamblaje de librerías propias o de terceros.

Conclusión

El This Namespace Proxy supone un cambio importante en cuanto al planteamiento de los patrones de diseño más tradicionales. El uso de fórmulas básicas del propio lenguaje como son la APIs call y apply permiten asociar métodos a un número ilimitados de contextos – objetos permitiendo una ejecución en paralelo de los mismos.

Llendo más allá, puede utilizarse esta estructura para conseguir que partes de un código (librerías enteras u otros métodos) actúen dentro de un determinado ámbito sin interferir con el resto disminuyendo drásticamente los riesgos de colisiones e incompatibilidades.

Más:

{8} Comentarios.

  1. Israel

    Muy bueno el artículo aunque no me ha quedado clara la diferencia entre usar «call» ó «aply» (a parte de lo evidente).

  2. pedro

    ¿como se consigue hacer una clase que herede de otra con este patron? Es decir, tengo mi clase var myApp = {};
    (function(){//lo que haga la clase}).apply( myApp ); y quiero crear una segunda clase usando este mismo patron que extienda de la primera… estoy intentandolo pero no doy con la tecla…

  3. pedro

    me lie, basta con myApp.mySubApp = {};
    (function(){//lo que haga la clase}).apply( myApp.mySubApp );

  4. maparrar

    Hola, hace un tiempo uso una estructura como la siguiente para generar objetos a partir de «clases» (entiendo que uno se debería olvidar de las clases en javascript), sin embargo me ha funcionado bastante bien. Noto que hay algún parecido con el patrón Proxy, sin usar apply. Quisiera saber si cometo errores al usar esta estructura, o si incluyo fallas de seguridad.

    Foo=function(param1,param2){
        //Create an obj variable to manage this without conflicts with another this uses
        var obj=this;
        //Private variables
        var priVar=0;
        var parameter1=param1;
        var parameter2=param2;
        
        //Private Methods
        var privateMethod=function(){
            console.debug("I'm a private method, only accesible from the Object");
        };
        
        //Public Methods
        obj.decrement=function(){
            console.debug(--priVar);
        }
        obj.increment=function(){
            console.debug(++priVar);
        }
        obj.getParam=function(){
            console.debug("The parameter1 passsed in constructor is: "+parameter1+" - parameter 2: "+parameter2);
        }
        obj.usePrivateMethod=function(){
            privateMethod();
        }
    };
    
    //Uso de la "clase"
    var foo=new Foo("parameterValue","another");
    foo.increment();
    foo.decrement();
    foo.getParam();
    foo.usePrivateMethod();
    
    • Carlos Benítez

      No hay ningún problema con la estructura que planteas; durante años, la programación con objetos en Javascript se ha desarrollado con un patrón similar al que propones: constructores y herencia prototípica.

      En tu caso, Foo, (que por cierto has olvidado declarar con ‘var’), actúa como un constructor al que se precisa instanciar como una ‘clase’ estándar; tras ello, mediante herencia prototípica, se van estableciendo sus métodos públicos dejando el resto de funciones internas como privadas.

      Los patrones ‘modernos’, lo que buscan es ‘homogeneizar’ la estructura interna de los objetos para organizarla en Javascript puro, es decir, sin tratar de imitar las características o funcionalidades a las que podemos estar acostumbrados en otros lenguajes. De ahí que ni siquiera hablemos de clases sino de prototipos y que optemos por esquemas donde todos los métodos internos de un objeto, se declaren de la misma forma para, solo en un determinado punto, hacerlos públicos o no.

      Como ves, es solo una cuestión de ‘organización’ interna… Tu código, o forma, es perfectamente válido.

      Saludos.

  5. Mario

    Hola, en la implementación del patrón módulo clásico me gustó mucho cuando pasamos el objeto window a nuestra función autoejecutable y de hecho noté un poco más rápido mi código (aparentemente).
    Mi duda es si esta ventaja no se pierde al utilizar el patron proxy.

  6. dhamaso

    Hola, yo utilizo este patrón de diseño (Por que lo aprendí aquí y gracias a usted) y me gustaría saber la mejor forma de utilizar la herencia con este tipo de estructura.

    he visto que Pedro plantea una solución:

    myApp.mySubApp

    ¿Pero esa es la mejor forma de hacer esto?

    De antemano gracias y un saludo desde México.

  7. dhamaso

    Otra vez yo; Probé el código de Pedro, pero en lugar de hacer una herencia lo que hace es crear un objeto dentro del objeto myApp:

    myApp.mySubApp

    mySubApp no tiene acceso a las propiedades y métodos del objeto padre, ni las puede sobrescribir.

    Y lo que yo intento hacer es algo como esto:

    /**
    * Clase Padre
    */
    var CORE = (CORE || {});
    (function(window,document,$,undefined){
    var self = this;
    var defaults = {
    ‘a’ : null,
    ‘b’ : null
    };
    this.init = function(options){
    self.settings = $.extend({}, defaults, options);
    return this;
    };
    this.run = function(){
    // hacer algo aqui
    };
    this.sayHi = function(){
    return defaults.a + ‘ ‘ + defaults.b;
    };
    }).apply(CORE, [window, window.document, jQuery]);

    /**
    * Clase hija
    */
    var APP = (APP || {});
    (function(window,document,$,undefined){
    this.run = function(){
    // Sobrescribir el método padre
    };
    }).apply(APP, [window, window.document, jQuery]);

    // Poder llamar
    APP.sayHi();

    ¿Algo así se puede hacer con este patrón?

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *