Desde que empecé a jugar con Javascript hace ya unos cuantos meses no he podido evitar comparar continuamente la forma en que se hacen las cosas en Javascript, un lenguaje dinámico, con la forma en que se hacen las cosas en C#, un lenguaje estático que además es el entorno en que mejor me muevo.
Obviamente he encontrado cosas que me gusta más hacer en Javascript y cosas que me parecen más cómodas en C#, pero en este caso, más que de una cuestión de tareas concretas me gustaría tratar un asunto más general: la inyección de dependencias.
En lenguajes estáticos la inyección de dependencias es necesaria muy útil para poder cambiar la implementación usada de cada componente, que suele estar representado por un interfaz. Es una técnica muy habitual para poder escribir tests unitarios (y a veces únicamente para eso, cosa que algunos creen que no es una buena idea).
En el caso de un lenguaje dinámico como Javascript, en el que es posible redefinir al vuelo los métodos de una clase, se puede conseguir cambiar la implementación fácilmente sin necesidad ni de crear interfaces (que, de hecho, no existen), ni nuevas implementaciones de interfaces (es lógico, si no existen, es difícil crear nuevas implementaciones).
Pero la inyección de dependencias no sólo es útil cuando tenemos varias implementaciones de un interfaz. La inyección de dependencias por si misma ayuda a crear mejor código, más estructurado, con clases de responsabilidad más definida y más desacopladas entre sí, ya que la inyección de dependencias independiza a las clases de elegir cuáles van a ser sus colaboradores.
¿Cómo se implementa la inyección de dependencias en Javascript? Pues igual que en cualquier otro lenguaje. La inyección de dependencias consiste en pasar a un objeto sus dependencias como parámetros en el constructor, lo que se puede hacer sin problemas en Javascript.
Veamos un ejemplo sencillo. Si tenemos un ViewModel
de Knockout que debe cargar datos de un servidor externo usando AJAX, podríamos implementar algo así:
function ViewModel() { var self = this; self.books = ko.observableArray(); // ... some other observable properties self.loadByCategory = function(category) { $.ajax({ url: '/api/category/', type: 'POST', dataType: 'json', data: JSON.stringify({category: category}), contentType: 'application/json; charset=utf-8', success: function(booksInThisCategory) { self.books(booksInThisCategory); }, }); }; }
En este ejemplo el ViewModel
está mezclando dos resposabilidades diferentes, representar la información de una forma adecuada a la vista y descargar la información de un servidor. Esto es una clara violación del SRP (Principio de Responsabilidad Única) y podemos solventarlo separando la responsabilidad en dos objetos diferentes:
function Server() { this.findBooksByCategory = function(category, success) { $.ajax({ url: '/api/category/', type: 'POST', dataType: 'json', data: JSON.stringify({category: category}), contentType: 'application/json; charset=utf-8', success: success, }); }; } function ViewModel(server) { var self = this; self.books = ko.observableArray(); self.loadByCategory = function(category) { server.findBooksByCategory(category, function(booksInCategory) { self.books(booksInCategory); }; }; } var server = new Server(); var model = new ViewModel(server);
Ahora la responsabilidad de descargar datos recae en el objeto server
, independizando ViewModel
de la procedencia de esos datos. Este ejemplo puede resultar poco idiomático para Javascript porque está calcado de lo que se haría en C#, pero pensad que server
no tiene porque ser una instancia de la clase Server
, podría ser cualquier objeto siempre y cuando tuviera un método findBooksByCategory
.
Es la ventaja de Javascript, al ser un lenguaje dinámico, podemos aprovechar el duck typing y no hace falta definir el tipo de la dependencia.
Hasta el momento no he dicho nada de contenedores de inversión de control. Como ya he explicado anteriormente no hace falta un contenedor IoC para hacer inyección de dependencias. Además, en los lenguajes dinámicos se pierde parte de la gracia de los contenedores porque en los constructores de las clases no está especificado el tipo de las dependencias y si queremos usar un contenedor nos obliga a definir de forma un tanto artificial las dependencias entre nuestros objetos para que el contenedor pueda componer unos con otros.
El duck typing que antes veíamos como una ventaja, se convierte en un inconveniente a la hora de utilizar contenedores de inversión de control. De todas formas, existen contenedores IoC para Javascript como wire.js o squirrel y seguramente haya casos en los que merezca la pena utilizarlos.
En cualquier caso, y aunque a algunos no les haga mucha gracia, está claro las aplicaciones desarrolladas con Javascript + HTML + CSS son cada vez más numerosas y también más complejas/completas. Es importante recordar que estamos desarrollando software, no ñapeando una página web, y por tanto debemos aplicar lo que ya sabemos sobre desarrollo de software (SOLID, DRY, KISS y todas las siglas que tanto nos gustan).
Pingback: Swiftcore.js: un contenedor IoC para Javascript « Koalite's blog
Hola muy bueno el ejemplo, yo lo hacia tal cual, nada mas que tenia los servicios en un js aparte que llamaba y luego bindeaba a los atributos de Knockout, lo que no puedo hacer ahora es en determinada parte de un código en angularjs, traerme(cargar) dinamicamente un services.js y ejecutarlo en el momento, el services tiene las funciones que hacen el ajax para hacer el crud, alguna idea ? use el $.getscript de jquery pero me trae el js en un string , no lo puedo ejecutar
Hola Ezequiel,
La verdad es que no sé muy bien lo que quieres hacer. AngularJS tiene su propio sistema de módulos e inyección de dependencias, por lo que no creo que sea necesario hacerlo «a mano». Tienes más información aquí: http://docs.angularjs.org/guide/di
Un saludo,
Juanma
Supongo que en la clase Server también hay una violación ya que dicha clase necesita conocer del funcionamiento de jQuery no? Tal vez esto podría solventarse pasando como dependencia a Server una función obtenedora de contenido por ajax que tenga X metodos. En este caso se le pasaría $.ajax al hacer la instancia. A mi algo que me vuelve loco es saber donde debo parar en separar responsabilidades únicas. Cuando una clase depende de muchas dependencias ya me entra el tembleque por que supongo que es sinónimo de que algo no está bien ideado.
Hola Paco,
Todo depende de cómo sea la aplicación completa y hasta dónde quieras llegar.
En este caso, si Server encapsulase todas las peticiones ajax, yo no abstraería más jQuery, ya que todo su uso quedaría limitado al objeto server.
Si hubiese muchas clases haciendo peticiones ajax, es posible que tuviese sentido encapsular el $.ajax de alguna forma.
Un saludo,
Juanma