Safe Type Classes con Javascript Puro

Hoy en día tenemos TypeScript, un superset de Javascript type safe de Microsoft, y Dart, otro superset type safe pero de Google, y estan muy bien, pero digamos que no tienes el tiempo ni/o la voluntad de aprender otro lenguaje o superset de uno que ya sabes, y quieres implementar algo de type safety en puro Javascript, como es mi caso.

Es difícil hacerlo para variables, no podemos hacer:

 string name;

Pero si que podemos tener buenas prácticas para estructuras de datos, clases vaya. El escenario es digamos una sistema de mensajes, que tiene que tener una estructura definida, normalmente usamos un simple objeto:

 { key: value, key2: value2 }.

Sin embargo, por el mismo dinamismo de Javascript, estamos sujetos a errores al reutilizar o construir el objeto en distintas partes del código o en otro componente, por ejemplo en el sitio web y en una aplicación móvil o de lado del servidor.

Aqui nos serviría que el lenguaje sea type safe, pero no es así. Lo que podemos hacer es lo siguiente:

  1. Implementar funciones que hagan de interfaces.
  2. Crear una funcion que haga de clase Factory y la utilizemos para construir las clases.
  3. Implementar validación de tipos dentro de la clase Factory.

Vamos por partes. En este ejemplo vamos a crear dos interfaces, una simple y otra compuesta. La compuesta contendrá un dato de tipo de la interfaz simple. Vamos a verlo en código:
[javascript]
// Vamos a necesitar una excepcion:
/**
* Exception class
*
* @return {undefined}
*/
function Exception(message, reference) {
this.message = message;
this.reference = reference;
};

/**
* Representa una posición en el mapa
*/
function IPosition() {
// La latitud
this.lat = 0.0;
// La longitud
this.lng = 0.0;
} // end interface IPosition

/**
* Representa un lugar en el mapa
*/
function IPlace() {
// El nombre del lugar
this.name = “”;
// La dirección del lugar
this.formattedAddress = “”;
// La posición del lugar
this.position = new IPosition();
} // end interface IPlace
[/javascript]

Luego, vamos a implementar una function Factory que haga de clase Factory para crear nuestros objetos, con tipos válidos. Esta tendrá un método validador y un método de creación para cada clase que deseemos crear instancias:
[javascript]
/**
* This class provides constructors for interfaces
*
* @return {undefined}
*/
function Factory() {

// Usamos esta variable privada para referirnos a nuestra clase
// de manera segura, en toda la clase, para no utilizar “this”
// ya que puede cambiar de referencia, dependiendo del alcance
var self = this;

/**
* Valida los argumentos comparando sus tipos con un arreglo de tipos
*
* @param {array} args Los argumentos a comparar
* @param {array} types Una lista con los tipos a comparar. Deben coincidir en índices
*
* @return {bool}
*/
self.validateArgumentsTypes = function(args, types) {

// Para cada argumento, válida contra el tipo
for (i = 0; i & lt; args.length; i++) {
var type = types[i];

// Si es funcion u objeto, valida tipo y constructor
if (typeof type == “function” || typeof type == “object”) {
// Valida tipo
if (typeof args[i] != typeof type) {
throw new Exception(
“Different type at index position ” + i, ” validateArgumentsTypes”
);
} // end not type

// Verificar constructor
if (args[i].constructor != type.constructor) {
throw new Exception(
“Different constructor at index position ” + i, ” validateArgumentsTypes”
);
} // end if constructors are different
return;
} // end if function or object

// Si no es funcion u objeto, solo compara los tipos
if (typeof args[i] != type) {
throw new Exception(“Different type at index position ” + i, ” validateArgumentsTypes”);
} // end if
} // end for

return true;
} // end function validateArgumentTypes

/**
* Crea un objeto position
* @param {number} lat La latitud
* @param {number} lng La longitud
*
* @return {IPosition}
*/
self.createPosition = function(lat, lng) {
// Primero, como es estricto, revisamos que sean efectivamente 3 argumentos
// como Javascript es dinámico, podemos omitor argumentos en las funciones,
// como tratamos de evitar esto, validaremos que sean tres exactamente
if (arguments.length != 2) {
throw new Exception(“Must set 2 arguments exactly”, “Factory.createPosition”);
} // end if all arguments present

// Ahora, vamos a validar los tipos, mandando llamar la funcion validadora
self.validateArgumentsTypes(
arguments, [“number”, “number”]
); // end validateArgumentsType call

// Si llegamos hasta aqui, los datos son válidos, entonces procedemos a crear el objeto y devolverlo
var position = new IPosition();
position.lat = lat;
position.lng = lng;
return position;
}; // end function createPosition

/**
* Crea un objeto Place, basado en la interfaz IPlace
* @param {string} name El nombre del lugar
* @param {string} formattedAddress La dirección del lugar
* @param {IPosition} positio La posición en el mapa del lugar
*
* @return {IPlace}
*/
self.createPlace = function(name, formattedAddress, position) {
// Primero, como es estricto, revisamos que sean efectivamente 3 argumentos
// como Javascript es dinámico, podemos omitor argumentos en las funciones,
// como tratamos de evitar esto, validaremos que sean tres exactamente
if (arguments.length != 3) {
throw new Exception(“Must set 3 arguments exactly”, “Factory.createPlace”);
} // end if all arguments present

// Ahora, vamos a validar los tipos, mandando llamar la funcion validadora
self.validateArgumentsTypes(
arguments, [“string”, “string”, new IPosition()]
); // end validateArgumentsType call

// Si llegamos hasta aqui, los datos son válidos, entonces procedemos a crear el objeto y devolverlo
var place = new IPlace();
place.name = name;
place.formattedAddress = formattedAddress;
place.position = position;
return place;
}; // end function createPlace
} // end function Factory
[/javascript]
Y la utilizamos de la siguiente manera:

[javascript]
var factory = new Factory();
var place = factory.createPlace(
“myPlace”,
“This is my address”,
factory.createPosition(25.6487281, -100.4431836)
); // end createPlace
[/javascript]

Esto nos provocará errores:
[javascript]
var factory = new Factory();
var place
= factory.createPlace(
“myPlace”,
“This is my address”,
[ 25.6487, -100.4431 ]
); // end createPlace
var place
= factory.createPlace(
“myPlace”,
“This is my address”,
{} // si lo pasamos vacio
); // end createPlace
[/javascript]

Uncaught Exception {message: "Different constructor at index position 2", reference: " validateArgumentsTypes"}

Puedes pensar que esto es extramademente verboso y hasta bobo, pues puedes simplemente mandar llamar las funciones y usar un constructor para crearlas, sin tanto rollo. Sin embargo, si hacemos esto tendremos que tener validadores en cada constructor de cada clase, buscando que los argumentos esten definidos y luego verificando su tipo, si nos interesa que la validación sea estricta y segura.

A la larga, resultará en más código y aun más verboso. Esta aproximación es más ligera, escalable, replicable y elegante para mantener los tipos seguros en nuestras estructuras de datos, en todo el desarrollo.

Un saludo a todos, chic@s!

L.

Related Posts

7 extensiones a las cadenas en Javascript que ahorran mucho código y se ven elegantes

admin

March 26, 2016

javascript

No Comment

Estas 7 funciones básicas de cadenas son fáciles de implementar como extensiones del objeto String, son muy utilizadas y el código resultante es mucho más legible, mantenible y elegante: 1 y 2: Encode y Decode Uri A pesar de que las uso seguido, eso de añadir el sufijo “Component” a la función siempre se me […]

Read More

Agregar y quitar clases CSS a cualquier elemento DOM en Javascript

admin

March 23, 2016

javascript

No Comment

El siguiente script modifica el prototipo de HTMLElement para implementar la funcionalidad de agregar y quitar clases CSS fácilmente a cualquier elemento del DOM en javascript: [javascript] // Función para buscar la clase en el elemento HTMLElement.prototype.hasClass = function ( className ) { var rgx = new RegExp(‘(\\s|^)’ + className + ‘(\\s|$)’); var match = […]

Read More

Leave a Reply

Your email address will not be published. Required fields are marked *