Configuración sobre parametrización (O, una vuelta de rosca a los argumentos)

Generalmente, en el período de vida de un desarrollo es completamente normal que los requerimientos iniciales se vean modificados, así como tambien que la corrección de procedimientos o errores derive en un refactor de variada importancia.

Esta garantía de retrabajo supone estándares de desarrollo abierto a modificaciones (designing for change, open/closed principle). Dada su naturaleza dinámica, JavaScript carece de algunas herramientas básicas que poseen otros lenguajes (tipado, overloading, interfaces, etc).
En esta entrada se tratará de exponer puntualmente el uso de objetos como argumentos únicos, tanto para funciones y métodos como para los constructores.

Problematica

Como se mencionó previamente, las formas y objetivos de los desarrollos suelen mutar durante el período de vida del mismo. Las funciones y métodos puntualmente, suelen representar un punto crítico en este aspecto ya que su firma* dista de ser algo estatico.

*Se denomina firma (o signature en inglés) de un método a la identificación que puede realizarse en base a su nombre, número y calidad de argumentos.

Por ejemplo, supongamos que inicialmente el proyecto requiere de un método que realize un POST sobre una url dada. La implementación sería algo del tipo:

MyApp.request = function(url) {
	// .. implementacion
}

MyApp.request('/user/keep_alive');

Conforme mutan los requerimientos se le van agregando argumentos:

MyApp.request = function(url, method) {}
MyApp.request = function(url, method, params) {}
MyApp.request = function(url, method, params, headers) {}
MyApp.request = function(url, method, params, headers, successHandler) {}
MyApp.request = function(url, method, params, headers,
					successHandler, errorHandler) {}
MyApp.request = function(url, method, params, headers,
					successHandler, errorHandler, timeout) {}

Llegando al punto de que su uso se vuelve engorroso;

// Suponiendo que solo nos interesa completar la url y el timeout
MyApp.request('/user/status', null, null, null, null, null, 30);

Soluciones

Si usasemos algún lenguaje con overload, la implementación sería bastante sencilla (y digamos que engorrosa en casos donde varía demasiado la firma), en este caso Java:

public class Person {
	public Person() {
		// implementacion
	}

	public Person(String name) {
		// implementacion
	}

	public Person(String name, String lastname) {
		// implementacion
	}

	public Person(String name, String lastname, Integer age) {
		// implementacion
	}

	public Person(String name, Integer age) {
		// implementacion
	}

	// etc...
}

Volviendo a JavaScript, la solución, obvia para cualquier persona que haya utilizado alguna de las librerías más conocidas, consiste en agrupar todos o algunos de los argumentos en un objeto. Luego, para cada una de las propiedades, asignamos un valor por defecto.
Para asignar los valores por defecto, en primera instancia vamos a hacer uso de una pequeña utilidad

/**
 * Copia las propiedades del object source al object
 * target. No reemplaza valores existentes.
 *
 * @param {Object} target
 * @param {Object} source
 */
MyApp.extend = function(target, source) {
	// la lista es filtrada, para evitar copiar propiedades
	// inyectadas en los prototipos
	for(var prop in source) if(source.hasOwnProperty(prop)) {
		target[prop] = target[prop] || source[prop];
	}
}

Ya con nuestra navaja suiza, podemos definir facilmente nuestro kit de valores por defecto.

MyApp.request = function(config) {
	// definimos valores por defecto
	config = config || {};
	MyApp.extend(config, {
		url: 'user/status',
		method: 'POST',
		params: {},
		headers: {},
		successHandler: function(responseText) {},
		errorHandler: function(e) {},
		timeout: 30
	});

	// .. implementacion
}

// Ejecutamos el método con la url y el timeout:
MyApp.request({
	url: '/user/get_profile/',
	timeout: 0
});

Otra forma de asignar valores por defecto a una funcion o metodo (no constructor) consiste en utilizar un decorator, que complete con los valores dados toda configuración que reciba el método.

/**
 * Decora una funcion que espera un objeto de configuracion con valores
 * por defecto.
 *
 * @param {Function} fn
 * @param {Object} defaultConfig
 * @param {Object} context
 * @return {Function} La funcion con valores por defecto
 */
function defaultConfigDecorator(fn, defaultConfig, context) {
	defaultConfig = defaultConfig || {};
	return function(config) {
		config = config || {};	

		// Contexto o scope en el cual se aplica la funcion
		context = context || this;

		// Filtramos la lista
		for(var p in defaultConfig) if(defaultConfig.hasOwnProperty(p)) {
			config[p] = typeof(config[p]) != 'undefined' ?
							config[p] : defaultConfig[p];
		}

		// Devolvemos la funcion decorada
		return fn.call(context, config);
	}
}

/**
 * Crea un slideshow con la configuracion dada
 *
 * @param {Object} config
 */
function slideShow(config) {
	// implementacion
	console.log(config);
}

slideShow(); // undefined

/**
 * Crea un slideshow con la configuracion dada con
 * sus respectivos valores por defecto
 */
var defaultSlideShow = defaultConfigDecorator(slideShow, {
	target: document.getElementsByTagName('body')[0],
	title: 'Slideshow',
	fade: true,
	interval: 5
});

// Ejecutamos sin configuracion
defaultSlideShow(); // Object { target=, title="Slideshow", more...}
// Con configuracion parcial
defaultSlideShow({ title: 'MySlideShow' });
// Object { title="MySlideShow", target=, more...}


Deja un comentario

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

Comment

You may use these tags : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>