19 de Enero, 2014

Programación orientada a objetos en Javascript

Estoy intentando hacer un juego en Javascript usando canvas como parte de mi reto One Game a Month (ya hablaré de esto más adelante si consigo entregar algo :D).

El juego que estoy programando es mucho más sencillo de implementar usando un diseño orientado a objetos, así que he tenido que investigar un poco cómo funciona el tema en Javascript, y finalmente me he decidido por un enfoque funcional (con funciones) en lugar de usar prototipos como es más habitual (por ejemplo Introduction to Object-Oriented JavaScript), pero que no soporta propiedades privadas.

El tema es más o menos seguir una convención en todo tu proyecto, y para mi usar funciones ha resultado a la vez potente y sencillo.

La POO funcional en Javascript se puede resumir como:

var Clase = function(params) {
    // contenido publico
    var self = {
        nombre : "Clase",
        params : params,

        metodo_publico : function() {
            return true;
        }
    };

    // propiedades privadas
    var privado = 0;

    // propiedades progegidas
    self.protegido = "foo";
    self.metodo_protegido = function() {
        return false;
    };

    // como devolvemos 'self' que es un objeto creado con {}
    // no será necesario usar new
    return self;
};

Esa sería más o menos la estructura de una clase. Vamos a ver un ejemplo sencillo:

var Vehiculo = function(nombre, nruedas) {
    var self = {
        nombre : nombre
    };

    var nruedas = nruedas;

    self.get_nruedas = function() {
        return nruedas;
    };

    return self;
};

Con esto definimos un vehículo que tendrá dos propidades: nombre (público, podemos cambiarlo) y nruedas (número de ruedas, que es privado y no va a cambiar). Vamos a hacer alguna preba:

> v = Vehiculo("bicicleta", 2);
  Object {nombre: "bicicleta", get_nruedas: function}
> v.nombre; 
  "bicicleta"
> v.nruedas; 
  undefined
> v.get_nruedas();
  2

Esto es bastante básico. Ahora vamos a implementar herencia, que es lo que realmente necesito para mi juego. Definamos una subclase Coche que añade funcionalidad a un vehículo:

var Coche = function(fabricante, modelo, npuertas) {
    var self = Vehiculo("coche", 4);

    var npuertas = npuertas;

    self.get_npuertas = function() {
        return npuertas;
    };

    self.fabricante = fabricante;
    self.modelo = modelo;
    self.nombre += " (" + fabricante +" " + modelo +")";

    return self;
};

Bueno, es un ejemplo algo tonto, pero nos pemite ver cómo hemos modificado una propiedad pública y hemos añadido funcionalidad a lo que ya teníamos en la clase base:

> c = Coche("Opel", "Corsa", 3);
  Object {nombre: "coche (Opel, Corsa)", get_nruedas: function, get_npuertas: function, fabricante: "Opel", modelo: "Corsa"…}
> c.get_nruedas();
  2
> c.nombre;
  "coche (Opel Corsa)"
> v.nruedas; 
  undefined
> v.npuertas; 
  undefined

Esto es lo que estoy usando, y para mi es suficiente. Es importante destacar que esta estrategia nos permite reemplazar un método de la clase base en la clase derivada, pero no hay forma de llamar al método base para aumentar su fucionalidad (en el ejemplo he modificado una propidad pública premeditadamente, porque eso sí se puede hacer :P).

Douglas Crockford propone añadir el siguiente método a tipo Object:

Object.prototype.super = function(name) {
    var self = this, method = self[name];
    return function() {
        return method.apply(self, arguments);
    };
};

De forma que podríamos hacer algo como:

var Motocicleta = function(sidecar) {
    var self = Vehiculo("motocicleta", 2);

    self.sidecar = sidecar;

    var super_get_nruedas = self.super('get_nruedas');
    self.get_nruedas = function() {
        var ruedas = super_get_nruedas();
        if(self.sidecar) {
            ruedas += 2;
        }
        return ruedas;
    };

    return self;
};

Con lo que si nuestra motocicleta lleva sidecar, añadimos dos ruedas (menudo ejemplo):

> m = Motocicleta(false);
  Object {nombre: "motocicleta", get_nruedas: function, sidecar: false, super: function}
> m.get_nruedas();
  2
> m = Motocicleta(true);
  Object {nombre: "motocicleta", get_nruedas: function, sidecar: true, super: function}
> m.get_nruedas();
  4

Imagino que hay mejores formas de explicar todo esto, y yo no soy un experto en Javascript, pero escribir esta anotación me ha ayudado a aclarar mis ideas y me servirá para tener algunas notas cuando pase el tiempo y no me acuerde en qué estaba pensando cuando escribí el código ;).

Anotación por Juan J. Martínez, clasificada en: programming, javascript.

Hay 4 comentarios

Gravatar

Entrada muy interesante, seguro que se pueden aprovechar de alguna forma estos ejemplos para – además de juegos – desarrollo web. Creo que ya tengo para un rato ;).

por r0sk, en 2014-01-21 10:01:26

Gravatar

Gracias :)

Realmente está todo en “JavaScript: The Good Parts”, me parece que es uno de los capítulos “buenos” que tiene.

por Juanjo, en 2014-01-21 10:03:59

Gravatar

Buena explicación

Vengo de Java y cuando me topo con Javascript noto que me faltan cosas.

por jmzc, en 2014-01-31 08:41:24

Gravatar

Java y Javascript son lenguajes completamente diferentes; la coincidencia en el nombre es solo anecdótica :)

Lo que menos me gusta de Javascript es que hay patrones POO que “puedes implementar”; comparado con soporte real de POO queda un poco feo.

De hecho me he dado cuenta que no sigo mi ejemplo de “super” porque resulta más cómodo declarar una variable privada y asignarle el método base antes de declarar el nuevo método:

var _get_ruedas = self.get_ruedas;
self.get_ruedas = function() {
   var ruedas = _get_ruedas();
   ... etc ...
};

por Juanjo, en 2014-01-31 08:50:43

Los comentarios están cerrados: los comentarios se cierran automáticamente una vez pasados 30 días. Si quieres comentar algo acerca de la anotación, puedes hacerlo por e-mail.

Algunas anotaciones relacionadas: