domingo, 10 de noviembre de 2013

Herencia en javascript


Me he encontrado que es muy común que los programadores que trabajan para el ambiente web tengan la idea de que la herencia en javascript no es posible, pero realmente sí existe, su implementación no es tan evidente como en C# pero sí se puede realizar.

Vamos a crear una clase que se llamará Vehicle de la que heredaremos para crear la clase Car y Boat.

Comencemos por crear un proyecto en Visual Studio de tipo ASP NET Empty Web Site, ahí agregaremos una página llamada Inheritance.htm y comenzaremos abriendo una etiqueta para código javascript.

La clase Vehicle va a tener las propiedades:
  1. Model.
  2. Year.
  3. EngineSize.
También tendrá las siguientes funciones:
  1. IsStarted(), la cual devolverá un booleano indicando si el motor está encendido.
  2. Start(), marcará el motor del vehículo como encendido.
  3. Stop(), marcará el motor del vehículo como apagado.
Comenzaremos por crear la clase Vehicle con sus tres miembros públicos:
function Vehicle(model, year, engineSize) { this.model = model; this.year = year; this.engineSize = engineSize; }
Ahora vamos a agregar un miembro privado que guardará el estado que tiene el motor del vehículo y las funciones que permitirán la administración de ese estado:
function Vehicle(model, year, engineSize) { this.model = model; this.year = year; this.engineSize = engineSize; var engineIsStarted = false; this.isStarted = function() { return engineIsStarted; } this.start = function () { engineIsStarted = true; } this.stop = function () { engineIsStarted = false; } }
Aquí deberemos notar que al declarar engineIsStarted como variable, se convierte en un miembro privado y por lo tanto el que utilice una instancia de nuestra clase Vehicle no podrá manipular de forma directa a esta variable, sólo a través de las funciones que hemos creado.
Hasta ahora hemos creado lo que normalmente se haría para la declaración de una clase con algunos miembros públicos, un miembro privado y algunas funciones.
Para comenzar con la implementación de la herencia, deberemos empotrar el código que llevamos en una IIFE (Immediately Invoked Function Expression), asignarlo a una variable y regresar la instancia de la ejecución de la IIFE.    Nuestro código queda de la siguiente manera:
var Vehicle = (function(){ function Vehicle(model, year, engineSize) { this.model = model; this.year = year; this.engineSize = engineSize; var engineIsStarted = false; this.isStarted = function () { return engineIsStarted; } this.start = function () { engineIsStarted = true; } this.stop = function () { engineIsStarted = false; } } return Vehicle; }());
Ahora vamos a crear nuestra clase Car que heredará de Vehicle y aparte agregará un miembro público llamado wheelCount.    Comenzaremos creando nuestra clase Car como habitualmente lo haríamos, con su constructor recibiendo las cuatro propiedades que va a tener un carro (model, year, engineSize, wheelCount) pero sólo crearemos un miembro público llamado wheelCount dado que los otros tres los heredaremos de Vehicle:
function Car(model, year, engineSize, wheelCount) { this.wheelCount = wheelCount; }
Ahora vamos a empotrar el código en una IIFE tal como lo hicimos con Vehicle:
var Car = (function () { function Car(model, year, engineSize, wheelCount) { this.wheelCount = wheelCount; } return Car; }());
Para implementar la herencia, debemos especificar en la IIFE la clase de la que se va a heredar así como también agregar un parámetro a la IIFE donde se recibirá a la instancia de la clase padre:
var Car = (function (parent) { function Car(model, year, engineSize, wheelCount) { parent.call(this, model, year, engineSize); this.wheelCount = wheelCount; } return Car; }(Vehicle));
Para terminar la implementación debemos modificar el prototipo de la clase Car, el constructor y mandar a llamar al constructor de la clase base:
var Car = (function (parent) { Car.prototype = new Vehicle(); Car.prototype.constructor = Car; function Car(model, year, engineSize, wheelCount) { parent.call(this, model, year, engineSize); this.wheelCount = wheelCount; } return Car; }(Vehicle));
Vamos crear la clase Boat con la diferencia de que las propiedades extras que tendrá son width y length:
var Boat = (function (parent) { Boat.prototype = new Vehicle(); Boat.prototype.constructor = Boat; function Boat(model, year, engineSize, width, length) { parent.call(this, model, year, engineSize); this.width = width; this.length = length; } return Boat; }(Vehicle));
Con esto ya logramos que las clases Car y Boat heredaran de Vehicle, sólo falta probar que realmente funcione y esto lo haremos con otra IIFE:
(function () { var aCar = new Car('Chevy', 2000, 1.6, 4); alert('Car isStarted: ' + aCar.isStarted()); aCar.start(); alert('Car isStarted: ' + aCar.isStarted()); alert('Model: ' + aCar.model + '\nYear: ' + aCar.year + '\nEngine size: ' + aCar.engineSize + '\nWheel count: ' + aCar.wheelCount); var aBoat = new Boat('SL350', 2008, 0.7, 2, 3); alert('Boat isStarted: ' + aBoat.isStarted()); aBoat.start(); alert('Boat isStarted: ' + aBoat.isStarted()); alert('Model: ' + aBoat.model + '\nYear: ' + aBoat.year + '\nEngine size: ' + aBoat.engineSize + '\nWidth: ' + aBoat.width + '\nLength: ' + aBoat.length); }());
Cuando ejecutemos nuestro código veremos que la primera alerta nos dirá que el carro no está encendido, después de mandar a llamar a la función start() y volver a verificar el estado del carro, veremos que la alerta dirá que el carro ya está encendido.    Al mostrar la información con la tercera alerta, veremos que las propiedades fueron correctamente asignadas.

Con la parte del código que prueba la clase Boat el resultado será similar, a diferencia que mostrará Width y Length que son propias de la clase Boat.

¿Cosas que se pueden lograr con esto?, es difícil decirlo porque dependerá mucho de lo que quieres hacer y hasta dónde quieres llegar, pero seguro que ya tendrás una herramienta más para mejorar tu codificación.

Espero te haya resultado útil la entrada, nos vemos en la siguiente.