JavaScript y la programación orientada a objetos

2021-01-02 - Categorías: JavaScript
JavaScript

Con las tareas cotidianas de puesta al día, traigo este post sobre POO en JavasScript. Esto es un clásico de la programación, es un howto resumen de las principales características que disponemos en JavaScript para crear objetos. Para luego extenderlos creando otros derivados, y así poder estructurar los programas en JavaScript permitiendo que sean más mantenibles.

Ya sea que estemos implementando un listado de productos, un carrito, un listado de páginas web, un buscador, información del usuario, elementos de un juego 2D o 3D, etc.. podremos mover mucha lógica de la aplicación a la programación en cliente usando JavaScript.

Imaginemos entonces que queremos construir un juego en JavaScript, y queremos tener un montón de objetos. Vamos al grano..

POO básico

Lo primero que podemos hacer para crear un objeto, podría ser algo parecido a lo siguiente en un fichero app.js:

let theObject = {
    name: 'Nombre del objeto',
    description: 'Descripción del objeto'
};

Si estamos en una página web, podríamos incluir este código fuente y llamarlo desde un fichero index.html de pruebas:

<html>
    <body>
        <script src="app.js"></script>
    </body>
</html>

Al cargarlo en el navegador e inspeccionar veríamos que no hace nada, pero ya tenemos disponible la variable theObject que es un objeto con dos variables de tipo cadena. Las variables y funciones que puede tener un objeto con este tipo de notación son iguales a si se tratara de un JSON.

Se podría ampliar el fichero app.js con algo como lo siguiente:

// POO basic
let theObject = {
    name: 'Nombre del objeto',
    description: 'Descripción del objeto',
    position: {
        x: 0,
        y: 0
    },
    testFunction: function (parameter) {
        console.log('Función de pruebas: ' + parameter);
    },
    printPosition: function() {
        console.log('Current position of ' + this.name + ' is ' + this.position.x + ', ' + this.position.y);
    }
};
theObject.testFunction2 = function () {
    console.log('Say something.. ' + this.name);
}

console.log(theObject.name);
console.log(theObject['name']);
theObject.testFunction('prueba de parámetro');
theObject.testFunction2();
theObject.printPosition();

Esto nos mostraría en el inspector de la web, en la consola, los mensajes de la imagen siguiente:

JavaScript POO

POO con un constructor, declarando variables de un tipo de objeto

Lo siguiente más parecido a la programación orientada a objetos en otros lenguajes como PHP o Java, podría ser el definir tipos de objetos, para luego poder declarar variables de estos tipos. Esto se podría hacer usando funciones. Para hacer un constructor de objetos se podría hacer con algo parecido a lo siguiente:

// POO using constructor..
function MyObject() {
    this.name = 'Nombre del Objecto';
    this.description = 'Descripción del Objecto';
    this.position = {
        x: 0,
        y: 0
    };
    this.testFunction = function (parameter) {
        console.log('Función de pruebas: ' + parameter);
    };
    this.printPosition = function () {
        console.log('Current position of ' + this.name + ' is ' + this.position.x + ', ' + this.position.y);
    };
}

console.log('>>> Testing the POO constructor..');

let testNewObject = new MyObject();
testNewObject.testFunction('prueba de parámetro');
testNewObject.printPosition();

let testNewObject2 = new testNewObject.constructor();
testNewObject2.testFunction('prueba de parámetro');
testNewObject2.printPosition();

El resultado es el mismo que el anterior, pero ahora se pueden declarar variables de tipo MyObject, es decir, podremos declarar tantos objetos como necesitemos. El resultado por consola de JavaScript se debería de ver como la imagen siguiente:

Creando tipos de objetos a partir de otros definidos, la herencia de objetos

Lo siguiente que podremos querer hacer, sería el ampliar funcionalidades de un tipo de objeto. Imaginemos que por ejemplo queremos tener objetos, posicionados en ciertas coordenadas, del tipo anterior. Pero ahora estos objetos vamos a especificar que sean piezas de un tablero de ajedrez.

Esto podría hacerse con algo parecido a lo siguiente:

// POO inheritance..
function Tower() {
    MyObject.call(this);
    this.position = { x: 0, y:0 };
    this.type = 'tower';
    this.color = 'black';
}

function Horse() {
    MyObject.call(this);
    this.position = { x: 0, y:1 };
    this.type = 'horse';
    this.color = 'black';
}

console.log('>>> Trying to create derivated objects..');

let testNewObjectDerivated1 = new Tower();
testNewObjectDerivated1.printPosition();

let testNewObjectDerivated2 = new Horse();
testNewObjectDerivated2.printPosition();

Mejorando los constructores con parámetros

Para simplificar esta creación de subobjetos, se podría parametrizar, simplificando el código fuente y su uso:

'use strict';

// POO using constructor..
function MyObject(x, y, type, color) {
    this.position = {
        x: x,
        y: y
    };
    this.type = type;
    this.color = color;
    this.printAllInfo = function () {
        console.log('Current position is ' + this.position.x + ', ' + this.position.y + ' type is ' + this.type + ' and color is ' + this.color);
    };
}

// POO inheritance..
function Tower() {
    MyObject.call(this, 0, 0, 'tower', 'black');
}

function Horse() {
    MyObject.call(this, 0, 1, 'horse', 'black');
}

console.log('>>> Trying to create derivated objects..');

let testNewObjectDerivated1 = new Tower();
testNewObjectDerivated1.printAllInfo();

let testNewObjectDerivated2 = new Horse();
testNewObjectDerivated2.printAllInfo();

El resultado por consola debería de verse tal que así:

Declarando los tipos de objetos como clases y extendiéndolos

A fecha de hoy, la mayoría de navegadores ya soporta ECMAScript 2015, con lo que se podrían usar las palabras reservadas class y extends que también podemos encontrar en otros lenguajes de programación:

'use strict';

// POO using constructor..
class MyObject {
    constructor(x, y, type, color) {
        this.position = {
            x: x,
            y: y
        };
        this.type = type;
        this.color = color;
    }

    printAllInfo() {
        console.log('Current position is ' + this.position.x + ', ' + this.position.y + ' type is ' + this.type + ' and color is ' + this.color);
    };
}

// POO inheritance..
class Tower extends MyObject {
    constructor(x, y, color) {
        super(x, y, 'tower', color);
    }
}

class Horse extends MyObject {
    constructor(x, y, color) {
        super(x, y, 'horse', color);
    }
}

console.log('>>> Trying to create derivated objects..');

let testNewObjectDerivated1 = new Tower(0, 0, 'black');
testNewObjectDerivated1.printAllInfo();

let testNewObjectDerivated2 = new Horse(0, 0, 'black');
testNewObjectDerivated2.printAllInfo();

El resultado por consola es el mismo que el anterior.

Los getters y setters de las clases

Y para terminar, hay otra funcionalidad muy interesante para los objetos, que son los getters y los setters. Es una manera de engancharnos en el momento que asignamos valores a variables internas, o devolvemos dichos valores internos.

Es una forma más de encapsular los datos dentro de los objetos. A fecha en que escribo no es posible declarar variables privadas dentro de los objetos, aunque deduzco que en un futuro próximo se podrá. Con estos getters y setters podremos simular algo parecido.

Es más fácil leer el código fuente que explicarlo, por ejemplo:

'use strict';

// POO using constructor..
class MyObject {
    constructor(x, y, type, color) {
        this._privateData = {
            x: x,
            y: y
        };
        this.type = type;
        this.color = color;
    }

    printAllInfo() {
        console.log('Current position is ' + this.positionX + ', ' + this.positionY + ' type is ' + this.type + ' and color is ' + this.color);
    };

    set positionX(x) {
        this._privateData.x = x;
    }

    get positionX() {
        return this._privateData.x;
    }

    set positionY(y) {
        this._privateData.y = y;
    }

    get positionY() {
        return this._privateData.y;
    }
}

// POO inheritance..
class Tower extends MyObject {
    constructor(x, y, color) {
        super(x, y, 'tower', color);
    }
}

class Horse extends MyObject {
    constructor(x, y, color) {
        super(x, y, 'horse', color);
    }
}

console.log('>>> Trying to create derivated objects..');

let testNewObjectDerivated1 = new Tower(0, 0, 'black');
testNewObjectDerivated1.positionX = 3;
testNewObjectDerivated1.positionY = 3;
testNewObjectDerivated1.printAllInfo();

let testNewObjectDerivated2 = new Horse(0, 0, 'black');
testNewObjectDerivated2.positionX = 1;
testNewObjectDerivated2.positionY = 3;
testNewObjectDerivated2.printAllInfo();

Deja una respuesta

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

 

© 2024 JnjSite.com - MIT license

Sitio hecho con WordPress, diseño y programación del tema por Jnj.