3

Inyección de Dependencias en CRIAX-SDK I

24Jun
en CRIAX-SDK, Documentación, Herramientas, Javascript, SDK, Tutoriales, XUL


Tomo como plantilla para redactar este post y otros sobre el mismo tema, del libro Desarrollo agil con Symfony 2.1 de Javier Eguiluz. Específicamente el Apendice B: Inyección de Dependencias (pag 577).

1.Entendiendo la Inyección de Dependencias

Imagina que quieres que tu aplicación pueda generar mensajes de depuración con información sobre las operaciones que están ejecutando. Lo más sencillo sería crear un objeto de tipo logger que permita añadir en un archivo de log los diferentes tipos de mensajes de depuración:

//en Application.js
var logger = new inyeccion.model.Logger([]);
logger.info("Mensaje de informacion");
logger.error("Mensaje de error");

Para que no se pierda la información generada por la aplicación, lo lógico es guardar estos mensajes en un archivo. Para modularizar mejor la aplicación, se crea primero una clase genérica llamada
File con métodos que permitan manipular los contenidos de un archivo:

 var AppPath = criax.chromeless.lib.AppPath;
 var File = criax.chromeless.lib.File;
 var Path = criax.chromeless.lib.Path;

 /**
  * Modelo para File
  *
  * @class File
  * @public
  * @extends qx.criax.domain.DomainModel
  * @author Nilmar Sanchez Muguercia
  * @namespace inyeccion.model
  * @copyrigth
  * @license
  * @version 0.0.1
  *
  */

qx.Class.define("inyeccion.model.File",
{
    extend : criax.domain.DomainModel,

    /**
     * @property
     */
    properties :
    {
    	/**
    	 * propiedad para el archivo
    	 *
    	 * @name __file
    	 * @private
    	 *
    	 */
    	__file : {}
    },

    /**
     * metodo de inicializacion de la clase
     *
     * @constructor
     * @public
     * @param params {array}: arreglo de parametros iniciales
     *
     */

    construct : function(params)
    {
        this.base(arguments);
        var appPath = new AppPath();
        //archivo de logs en el directorio de resource
        var resorcePath = appPath.getResourceDir();
        var path = new Path(resorcePath);
        var logFilePath = path.join("logs.txt");

        var file = new File(logFilePath);
        this.__file = file.open("w");
    },

   /**
     * @method
     */
    members :
    {
        /**
         *  metodo para escribir en el log
         *
         * @method write
         * @public
         * @param content {String}
         * @return
         *
         */

        write : function(content){
        	this.__file.write(content+" ");
        	this.__file.flush();
        }
    }
});

Después, define otra clase llamada Logger que utilice la clase File anterior para guardar los mensajes en el archivo de log:

 var File = inyeccion.model.File;

 /**
  * Modelo para Logger
  *
  * @class Logger
  * @public
  * @extends qx.criax.domain.DomainModel
  * @author Nilmar Sanchez Muguercia
  * @namespace inyeccion.model
  * @copyrigth
  * @license
  * @version 0.0.1
  *
  */

qx.Class.define("inyeccion.model.Logger",
{
    extend : criax.domain.DomainModel,

    /**
     * @property
     */
    properties :
    {
    	/**
    	 * propiedad para el archivo de log
    	 *
    	 * @name _file
    	 * @protected
    	 *
    	 */
    	_file : {}
    },

    /**
     * metodo de inicializacion de la clase
     *
     * @constructor
     * @public
     * @param params {array}: arreglo de parametros iniciales
     *
     */

    construct : function(params)
    {
        this.base(arguments);
        this._file = new File([]);
    },

   /**
     * @method
     */
    members :
    {
        /**
         *  metodo para mostrar un log de informacion
         *
         * @method info
         * @public
         * @param message {String}: mensaje de informacion
         * @return
         *
         */

        info : function(message){
            this._file.write("INFO: "+message);
        },

        /**
         *  metodo para mostrar un log de error
         *
         * @method error
         * @public
         * @param message {String}: mensaje de error
         * @return
         *
         */

        error : function(message){
            this._file.write("ERROR: "+message);
        }
    }
});

Las dos clases File y Logger son suficientes para que el siguiente código funcione correctamente y los mensajes se guarden en un archivo de log (en el directorio de resource):

//en Application.js
var logger = new inyeccion.model.Logger([]);
logger.info("Mensaje de informacion");
logger.error("Mensaje de error");

Hasta aquí todo muy sencillo. El código desarrollado hasta el momento funciona bien y es suficiente para solucionar la funcionalidad solicitada. Sin embargo, si lo utilizas en una aplicación real, pronto te darás cuenta de una carencia importante. ¿Cómo se modifica el archivo en el que se guardan los mensajes? Por ejemplo para que cada componente utilice su propio archivo de log o para que cada entorno de
ejecución (desarrollo o producción) guarden sus mensajes en archivos diferentes.

Una posible solución sería pasar la ruta del archivo en el propio constructor de la clase Logger, que a su vez lo pasaría al constructor de la clase File:

En la clase Logger:

 var File = inyeccion.model.File;

 /**
  * Modelo para Logger
  *
  * @class Logger
  * @public
  * @extends qx.criax.domain.DomainModel
  * @author Nilmar Sanchez Muguercia
  * @namespace inyeccion.model
  * @copyrigth
  * @license
  * @version 0.0.1
  *
  */

qx.Class.define("inyeccion.model.Logger",
{
    extend : criax.domain.DomainModel,

    /**
     * @property
     */
    properties :
    {
    	/**
    	 * propiedad para el archivo de log
    	 *
    	 * @name _file
    	 * @protected
    	 *
    	 */
    	_file : {}
    },

    /**
     * metodo de inicializacion de la clase
     *
     * @constructor
     * @public
     * @param params {array}: arreglo de parametros iniciales
     *        params[0] = ruta del archivo log
     */

    construct : function(params)
    {
        this.base(arguments);

        this._file = new File([params[0]]);
    },

   /**
     * @method
     */
    members :
    {
        /**
         *  metodo para mostrar un log de informacion
         *
         * @method info
         * @public
         * @param message {String}: mensaje de informacion
         * @return
         *
         */

        info : function(message){
            this._file.write("INFO: "+message);
        },

        /**
         *  metodo para mostrar un log de error
         *
         * @method error
         * @public
         * @param message {String}: mensaje de error
         * @return
         *
         */

        error : function(message){
            this._file.write("ERROR: "+message);
        }
    }
});

En la clase File:

var File = criax.chromeless.lib.File;

 /**
  * Modelo para File
  *
  * @class File
  * @public
  * @extends qx.criax.domain.DomainModel
  * @author Nilmar Sanchez Muguercia
  * @namespace inyeccion.model
  * @copyrigth
  * @license
  * @version 0.0.1
  *
  */

qx.Class.define("inyeccion.model.File",
{
    extend : criax.domain.DomainModel,

    /**
     * @property
     */
    properties :
    {
    	/**
    	 * propiedad para el archivo
    	 *
    	 * @name __file
    	 * @private
    	 *
    	 */
    	__file : {}
    },

    /**
     * metodo de inicializacion de la clase
     *
     * @constructor
     * @public
     * @param params {array}: arreglo de parametros iniciales
     *        params[0] = ruta del archivo de log 
     */

    construct : function(params)
    {
        this.base(arguments);

        var file = new File(params[0]);
        this.__file = file.open("w");
    },

   /**
     * @method
     */
    members :
    {
        /**
         *  metodo para escribir en el log
         *
         * @method write
         * @public
         * @param content {String}
         * @return
         *
         */

        write : function(content){
        	this.__file.write(content+" ");
        	this.__file.flush();
        }
    }
});

En el Application.js

//en Application.js
var appPath = new AppPath();
//archivo de logs en el directorio drecursos
var resorcePath = appPath.getResourceDir();
var path = new Path(resorcePath);
var logFilePath = path.join("logs.txt");

var logger = new inyeccion.model.Logger([logFilePath]);
logger.info("Mensaje de informacion");
logger.error("Mensaje de error");

El problema de esta solución es que tienes que indicar la ruta del archivo de log siempre que quieras escribir un mensaje. Así que en una aplicación real, acabarías escribiendo cientos de veces
la instrucción var logger = new inyeccion.model.Logger([logFilePath]);. Además, si la ruta del archivo cambia alguna vez, tendrías que cambiarla cientos de veces en toda la aplicación.

Suponiendo que lo anterior no sea un problema, imagina el siguiente escenario: desarrollas tu aplicación pensando que los mensajes de log se guardan en un archivo y tu cliente hace un cambio de última hora exigiendo que los mensajes se guarden en una base de datos en vez de en un archivo.

La solución a estos problemas consiste en refactorizar la clase Logger para que no dependa de la clase File. En su lugar, se utiliza una variable llamada manager que se pasa al constructor de la clase Logger:

 /**
  * Modelo para Logger
  *
  * @class Logger
  * @public
  * @extends qx.criax.domain.DomainModel
  * @author Nilmar Sanchez Muguercia
  * @namespace inyeccion.model
  * @copyrigth
  * @license
  * @version 0.0.1
  *
  */

qx.Class.define("inyeccion.model.Logger",
{
    extend : criax.domain.DomainModel,

    /**
     * @property
     */
    properties :
    {
    	/**
    	 * manager para guardar los logs
    	 *
    	 * @name __manager
    	 * @private
    	 *
    	 */
    	__manager : {}
    },

    /**
     * metodo de inicializacion de la clase
     *
     * @constructor
     * @public
     * @param params {array}: arreglo de parametros iniciales
     *        params[0] = manager
     *
     */

    construct : function(params)
    {
        this.base(arguments);
        this.__manager = params[0];
    },

   /**
     * @method
     */
    members :
    {
        /**
         *  metodo para mostrar un log de informacion
         *
         * @method info
         * @public
         * @param message {String}: mensaje de informacion
         * @return
         *
         */

        info : function(message){
            this.__manager.write("INFO: "+message);
        },

        /**
         *  metodo para mostrar un log de error
         *
         * @method error
         * @public
         * @param message {String}: mensaje de error
         * @return
         *
         */

        error : function(message){
            this.__manager.write("ERROR: "+message);
        }
    }
});

Después de este cambio, la clase Logger no sólo no depende de File, sino que ya no es su
responsabilidad crear ni configurar el objeto que guarda los mensajes. El Logger sólo sabe que alguien le pasará un objeto ya creado y que para guardar los mensajes podrá utilizar el método write() de ese objeto. Por tanto, ahora puedes guardar fácilmente los mensajes en un archivo, en una base de datos o en cualquier otro lugar:

var appPath = new AppPath();
//archivo de logs en el directorio drecursos
var resorcePath = appPath.getResourceDir();
var path = new Path(resorcePath);
var logFilePath = path.join("logs.txt");

var file = new inyeccion.model.File([logFilePath]);

var logger = new inyeccion.model.Logger([file]);
logger.info("Mensaje de informacion");
logger.error("Mensaje de error");

¡Esto es la inyección de dependencias! La inyección de dependencias consiste en crear y configurar primero el objeto File y pasárselo después a la clase Logger. En otras palabras, la inyección de dependencias consiste en pasar (inyectar) a las clases todos los objetos que necesitan (dependencias) ya creados y configurados.

Aunque es un cambio aparentemente sutil, observa cómo ahora es muy sencillo cambiar los requerimientos de la aplicación (al Logger no le importa si le pasas File o cualquier otro objeto), simplemente debe tener el metodo write().

Para ir ma allá y obligar a que ocurra esto, simplemente a las clases que trataran con los mensajes de log, les decimos que implementen una interfaz de tipo ISaveLog:

La interfaz ISaveLog:

 /**
  * Interfaz para las clases que trabajan con los mensajes de log
  *
  * @name ISaveLog
  * @default
  * @author Nilmar Sanchez M
  * @namespace inyeccion.model
  * @copyrigth
  * @license
  * @version 0.0.1
  *
  */

qx.Interface.define("inyeccion.model.ISaveLog",
{
    /**
     * @method
     */
    members :
    {
        /**
         *  metodo para escribir en el log
         *
         * @method write
         * @public
         * @param content {String}
         * @return
         *
         */

        write : function(content){}
    }
});

La clase File implementando la interfaz ISaveLog:

var File = criax.chromeless.lib.File;

 /**
  * Modelo para File
  *
  * @class File
  * @public
  * @extends qx.criax.domain.DomainModel
  * @author Nilmar Sanchez Muguercia
  * @namespace inyeccion.model
  * @copyrigth
  * @license
  * @version 0.0.1
  *
  */

qx.Class.define("inyeccion.model.File",
{
    extend : criax.domain.DomainModel,

    //que implemente la interfaz ISaveLog
    implement : inyeccion.model.ISaveLog,

    /**
     * @property
     */
    properties :
    {
    	/**
    	 * propiedad para el archivo
    	 *
    	 * @name __file
    	 * @private
    	 *
    	 */
    	__file : {}
    },

    /**
     * metodo de inicializacion de la clase
     *
     * @constructor
     * @public
     * @param params {array}: arreglo de parametros iniciales
     *
     */

    construct : function(params)
    {
        this.base(arguments);

        var file = new File(params[0]);
        this.__file = file.open("w");
    },

   /**
     * @method
     */
    members :
    {
        /**
         *  metodo para escribir en el log
         *
         * @method write
         * @public
         * @param content {String}
         * @return
         *
         */

        write : function(content){
        	this.__file.write(content+" ");
        	this.__file.flush();
        }
    }
});

La clase Logger preguntando si el objeto pasado al constructor implementa la interfaz ISaveLog:

 /**
  * Modelo para Logger
  *
  * @class Logger
  * @public
  * @extends qx.criax.domain.DomainModel
  * @author Nilmar Sanchez Muguercia
  * @namespace inyeccion.model
  * @copyrigth
  * @license
  * @version 0.0.1
  *
  */

qx.Class.define("inyeccion.model.Logger",
{
    extend : criax.domain.DomainModel,

    /**
     * @property
     */
    properties :
    {
    	/**
    	 * manager para guardar los logs
    	 *
    	 * @name __manager
    	 * @private
    	 *
    	 */
    	__manager : {}
    },

    /**
     * metodo de inicializacion de la clase
     *
     * @constructor
     * @public
     * @param params {array}: arreglo de parametros iniciales
     *        params[0] = manager
     *
     */

    construct : function(params)
    {
        this.base(arguments);
        //ver que el objeto pasado implementa la interfaz ISaveLog
        criax.oop.TypeHinting.tHInterface(inyeccion.model.ISaveLog,params[0]);
        this.__manager = params[0];
    },

   /**
     * @method
     */
    members :
    {
        /**
         *  metodo para mostrar un log de informacion
         *
         * @method info
         * @public
         * @param message {String}: mensaje de informacion
         * @return
         *
         */

        info : function(message){
            this.__manager.write("INFO: "+message);
        },

        /**
         *  metodo para mostrar un log de error
         *
         * @method error
         * @public
         * @param message {String}: mensaje de error
         * @return
         *
         */

        error : function(message){
            this.__manager.write("ERROR: "+message);
        }
    }
});

2.Tipos de Inyección de Dependencias en CRIAX-SDK

El código anterior inyecta las dependencias mediante el constructor constructor injection, que es el caso más común. Pero también se pueden inyectar mediante los métodos setter de la clase setter injection:

 /**
  * Modelo para Logger
  *
  * @class Logger
  * @public
  * @extends qx.criax.domain.DomainModel
  * @author Nilmar Sanchez Muguercia
  * @namespace inyeccion.model
  * @copyrigth
  * @license
  * @version 0.0.1
  *
  */

qx.Class.define("inyeccion.model.Logger",
{
    extend : criax.domain.DomainModel,

    /**
     * @property
     */
    properties :
    {
    	/**
    	 * manager para guardar los logs
    	 *
    	 * @name __manager
    	 * @private
    	 *
    	 */
    	__manager : {}
    },

    /**
     * metodo de inicializacion de la clase
     *
     * @constructor
     * @public
     * @param params {array}: arreglo de parametros iniciales
     * params[0] = manager
     *
     */

    construct : function(params)
    {
        this.base(arguments);
    },

   /**
     * @method
     */
    members :
    {
        /**
         *  metodo para mostrar un log de informacion
         *
         * @method info
         * @public
         * @param message {String}: mensaje de informacion
         * @return
         *
         */

        info : function(message){
            this.__manager.write("INFO: "+message);
        },

        /**
         *  metodo para mostrar un log de error
         *
         * @method error
         * @public
         * @param message {String}: mensaje de error
         * @return
         *
         */

        error : function(message){
            this.__manager.write("ERROR: "+message);
        },

        /**
         *  metodo para establecer el manager de los logs
         *
         * @method setManager
         * @public
         * @param manager {inyeccion.model.ISaveLog}
         * @return
         *
         */

        setManager : function(manager){
        	//ver que el objeto pasado implementa la interfaz ISaveLog
        	criax.oop.TypeHinting.tHInterface(inyeccion.model.ISaveLog,manager);
            this.__manager = manager;
        }
    }
});

En el Application.js

var appPath = new AppPath();
//archivo de logs en el directorio de recursos
var resorcePath = appPath.getResourceDir();
var path = new Path(resorcePath);
var logFilePath = path.join("logs.txt");

var file = new inyeccion.model.File([logFilePath]);

var logger = new inyeccion.model.Logger([]);
//setter inyection
logger.setManager(file);
logger.info("Mensaje de informacion");
logger.error("Mensaje de error");

A pesar de que tiene muchas ventajas, el problema de la inyección de dependencias es que antes de utilizar una clase, debes acordarte de crear y configurar correctamente todas sus dependencias.

Afortunadamente esto también tiene solución y se llama contenedor de inyección de dependencias que veremos en el proximo post.


Source de la aplicación: solo crear un proyecto llamado inyeccion y sustituir el directorio source, por el adjunto descompactado.

Source

0 MB

3 Comentarios

  1. Yosbel Marin dice:

    Para crear los mapas de persistencia, el objeto de configuracion tiene un atributo que se llama «entitys». «Entidad» en ingles es «entity» y su correspondiente prural es «entities». Si el objetivo es poner «entidad» plural en ingles entonces hay un error.

Dejar un comentario

¿Eres humano? Entonces resuelve esta operación: * Límite de tiempo se agote. Por favor, recargar el CAPTCHA por favor.