Interfaces en Javascript


Una de las primeras cosas que he echado de menos en javascript son las interfaces. Ya sé que la mayoría de los programadores de Javascriot no sabéis lo que son. Seguramente en algún rato libre escribiré sobre ellas con más detalle.
De momento, baste decir que son tremendamente útiles cuando ouno intenta implementar patrones de diseño como el Strategy o el Command. Y son, de hecho, una de las piedras angulares de la programación orientada a objetos en cualquier lenguaje.
En javascript no existen :-(. Pero hay formas, más o menos retorcidas, de implementar algo parecido. (Ver Pro Javascript Design Patterns, de Ross Harmes y Dustin Diaz, Apress 2007).

// Constructor.
var Interface = function(name, methods) {
    if(arguments.length != 2) {
        throw new Error("Interface constructor called with " + arguments.length +
            "arguments, but expected exactly 2.");
    }
    this.name = name;
    this.methods = [];
    for(var i = 0, len = methods.length; i < len; i++) {
        if(typeof methods[i] !== 'string') {
            throw new Error("Interface constructor expects method names to be "
                + "passed in as a string.");
        }
        this.methods.push(methods[i]);
    }
};
// Static class method.
Interface.ensureImplements = function(object) {
    if(arguments.length < 2) {
        throw new Error("Function Interface.ensureImplements called with " +
            arguments.length + "arguments, but expected at least 2.");
    }
    for(var i = 1, len = arguments.length; i < len; i++) {
        var interface = arguments[i];
        if(interface.constructor !== Interface) {
            throw new Error("Function Interface.ensureImplements expects arguments"
                + "two and above to be instances of Interface.");
        }
        for(var j = 0, methodsLen = interface.methods.length; j < methodsLen; j++) {
            var method = interface.methods[j];
            if(!object[method] || typeof object[method] !== 'function') {
                throw new Error("Function Interface.ensureImplements: object "
                    + "does not implement the " + interface.name
                    + " interface. Method " + method + " was not found.");
            }
        }
    }
};

Para utilizarlas hay que instanciar primero la interfaz, pasando los métodos que ha de implementar cualquier clase que la utilice:


var IPajaro =  new Interface('IPajaro', ['anda', 'vuela', 'diAlgo']);

Cuando creemos la clase Pato deberemos implementar, como mínimo, los métodos especificados en el Array del segundo parámetro:


var Pato =  function () {	// Implementa la interfaz IPajaro
	var cuack = function () {
		// Implementación del método cuack
	}
	
	this.anda = function () {
		// Implementación del método andar
	};
	this.nada = function () {
		// Implementación del método nadar
	};
	this.vuela = function () {
		// Implementación del método volar
	};
	this.diAlgo = cuack;
}

¿Por qué querríamos hacer esto? Porque si queremos hacer nuestro código realmente flexible querremos utilizar cualquier tipo de pájaro en nuestro código, y querremos comprobar que cuando usemos un pájaro implemente realmente los métodos que queramos lanzar.
Si no utilizamos la interfaz, la única manera eficiente de hacerlo es comprobar si el presunto objeto pajaro es una instancia de Pato, o de Aguila, o de Cuervo,… mal asunto, porque podemos tener muchos pájaros definidos, ¿Los vamos a comprobar todos? ¿Tendremos que escribir código específico para cada uno de ellos cada vez que queramos utilizar una de sus instancias?…

Para eso sirve una interfaz: establece un contrato para la API de un conjunto de objetos con características comunes, de forma que todos los que implementan la misma se puedan utilizar indistintamente en las partes de nuestro código que no necesiten saber cuál de ellos es exactamente. Así que si queremos hacer volar cualquier pájaro (y en nuestro caso concreto lo que tenemos es un pato) sólo tendremos que hacer lo siguiente:

var vuelaPajaro =  function (pajaro) {
	// Lanza un error si pajaro no implementa los métodos de IPajaro
	Interface.ensureImplements(pajaro, IPajaro);
	pajaro.vuela();
};

var patitoFeo = new Pato();

vuelaPajaro(patitoFeo);

De esta forma la implementación de la función vuelaPajaro() no depende de si el pájaro es un Aguila, un Pato, un Grajo, etc. y al mismo tiempo nos estamos asegurando de que al menos implementa los métodos necesarios para poder utilizarlo como un pájaro.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s