9. Objetos y arrays

9.2. Objetos

A medida que nuestros programas van creciendo en complejidad, hemos visto que suele ser conveniente encapsular partes del código en funciones o utilizar variables para evitar redundancias. Con la introducción de objetos en nuestro código daremos un paso más allá y nos adentraremos en la programación orientada a objetos (OPP, object-oriented programming), un paradigma de programación en el que comenzaremos a pensar en nuestros programas como en pequeñas estructuras que se combinan para crear otras estructuras más complejas. Siempre será mejor crear y mantener estructuras modulares más pequeñas de código que intentar escribir un programa monolítico con todas las funcionalidades.

Los objetos no dejan de ser una manera de vincular variables y funciones en una pequeña estructura. Como ya sabemos trabajar tanto con variables como con funciones, los objetos no serán más que una manera más conveniente y estructurada de combinar lo que ya sabemos hacer.

Propiedades y métodos

Estas variables y funciones que vinculamos, cuando hablamos de objetos, las llamaremos propiedades y métodos. Su funcionamiento es completamente el mismo que el de las variables y funciones que ya conocemos, pero cuando estén asociadas a un objeto nos referiremos a estas como propiedades y métodos.

De esta manera, un objeto no es más que un conjunto de valores (propiedades) asociados entre sí con una serie de acciones y comportamientos (métodos).

Pongamos como ejemplo que queremos crear un paisaje en nuestro sketch. Si seguimos la lógica de la programación orientada a objetos, podríamos pensar en el paisaje final como una estructura compleja compuesta de otras estructuras más pequeñas (flores, árboles, cielo, nubes…), y cada una de estas estructuras más pequeñas podría ser un objeto.

Si elegimos uno de estos objetos, la flor, por ejemplo, podríamos darle una serie de propiedades: posición, tamaño y número de pétalos. También algún comportamiento: crecer y que se muestre. Por lo tanto, deberemos contar con las siguientes variables y funciones:

let posX, posY;
let size;
let numPetals;

function grow(){}
function display(){}

Clases y objetos

Cada flor individual podrá tener distinto tamaño, posición o número de pétalos, pero todas las flores responderán siempre a esta misma estructura de propiedades y métodos. A esta estructura o plantilla de flor la llamamos clase y a cada una de las flores individuales que creemos a partir de la plantilla la llamaremos objeto. Por lo tanto, tendremos una clase flor con la que crearemos distintos objetos flor. Veamos ahora cómo crear una clase en P5.

Lo primero que debemos hacer es utilizar la palabra clave class seguida del nombre de nuestra clase, que por convención pondremos en mayúscula.

class Flower {
  // Nuestra clase (plantilla) para crear flores
}

A continuación, crearemos dentro de la clase su constructor, que define la forma en la que más adelante crearemos nuestros objetos. En el constructor definimos qué parámetros necesitaremos darle a nuestro objeto flor para su creación y su relación con las propiedades que definamos.

// De la misma manera que para crear un círculo tenemos:
// circle(posX, posY,)
// Para crear nuestra flor queremos algo así:
// Flower(posX, posY, numPetals);

De manera que nuestro constructor deberá ser algo similar a lo siguiente:

class Flower {
  constructor(posX, posY, numPetals) {
  }
}

Y dentro del constructor establecemos la relación entre estos parámetros y las propiedades de nuestra clase:

class Flower {
  constructor(posX, posY, numPetals) {
    this.posX = posX;
    this.posY = posY;
    this.numPetals = numPetals;
  }
}

Dentro de la clase estamos creando y accediendo a sus propiedades a través de la palabra clave this. De esta manera, le decimos al programa que no son unas variables cualesquiera, sino que se trata de propiedades relativas a esta (this) clase. A continuación, les asignamos valores que les pasamos como parámetro. De primeras, este código puede parecer algo confuso, ya que los nombres de los parámetros y de las propiedades son los mismos, aunque no tiene por qué ser así. Para hacer el código más comprensible (sobre todo cuando creamos nuestras primeras clases), puede resultar conveniente que por ejemplo a los parámetros les añadamos algún prefijo para aclararnos:

class Flower {
  constructor(_posX, _posY, _numPetals) {
    this.posX = _posX;
    this.posY = _posY;
    this.numPetals = _numPetals;
  }
}

O también así:

class Flower {
  constructor(pPosX, pPosY, pNumPetals) {
    this.posX = pPosX;
    this.posY = pPosY;
    this.numPetals = pNumPetals;
  }
}

En este caso, todas las propiedades que hemos definido dentro del constructor están asociadas a parámetros que le pasamos, pero no tiene que ser así. Podemos tener propiedades que definamos directamente:

class Flower {
  constructor(pPosX, pPosY, pNumPetals) {
    this.posX = pPosX;
    this.posY = pPosY;
    this.numPetals = pNumPetals;
    this.size = 20;
    this.flowerColor = “lightblue”;
  }
}

Tras haber definido el constructor de nuestra clase, mediante el cual crearemos los objetos, podemos definir métodos de la clase. Por ejemplo, podríamos tener un método para hacer crecer la flor:

class Flower {
  constructor(pPosX, pPosY, pNumPetals) {
    this.posX = pPosX;
    this.posY = pPosY;
    this.size = 20;
    this.numPetals = pNumPetals;
    this.flowerColor = "lightblue";
  }
  
  grow() {
    this.size *= 1.01;
  }
}

De esta manera, cada vez que llamemos al método grow(), el tamaño de la flor aumentará un 1 %. Si queremos visualizar la flor en nuestro sketch, deberemos crear otro método que se encargue de dibujarla utilizando sus propiedades:

class Flower {
  constructor(pPosX, pPosY, pNumPetals) {
    this.posX = pPosX;
    this.posY = pPosY;
    this.size = 20;
    this.numPetals = pNumPetals;
    this.flowerColor = “lightblue”;
  }
  
  grow() {
    this.size *= 1.1;
  }

  display() {
    for(let i = 0; i < this.numPetals; i++) {
      noStroke();
      fill(this.flowerColor);
      let angle = 2 * PI / this.numPetals * i;
      circle(this.posX + cos(angle) * this.size,
             this.posY + sin(angle) * this.size,
             this.size);
    }
    fill(250);
    circle(this.posX, this.posY, this.size * 1.5);
  }
}

Aquí vemos cómo accedemos a las propiedades de la clase usando this para utilizarlas a la hora de dibujar la flor. Para dibujar los pétalos, estamos utilizando un bucle con el que dibujamos cada pétalo en su ángulo correspondiente dividiendo 360º (2 × PI) entre el número de pétalos y multiplicándolo por el número de pétalo en el que nos encontremos.

Ya tenemos definida nuestra clase, con sus propiedades y sus métodos. Lo que debemos hacer ahora es crear algún objeto con esta «plantilla» que hemos creado.

Para ello, la sintaxis es la siguiente:

function setup(){
  createCanvas(400, 400);
  let flower = new Flower(width * 0.5, height * 0.5, 8);
}

Usamos la palabra clave new para definir que estamos creando un nuevo objeto de la clase Flower y, a continuación, entre paréntesis le pasamos los parámetros necesarios para su creación. En este caso, queremos la flor centrada en el canvas y con ocho pétalos.

Como con el resto de las variables, si queremos acceder a sus propiedades o llamar a sus métodos más adelante, es buena práctica que declaremos la variable al principio del código para que podamos acceder a esta tanto desde el setup() como  desde el draw(). Veamos el proceso entero de definición de la clase y creación de un objeto basado en la clase:

Figura 119. La clase Flower en acción
Fuente: elaboración propia.
Hemos escrito la clase al principio de todo, aunque podría estar en cualquier otra parte de nuestro código. De hecho, es muy común tener las clases que creemos en distintos archivos. De esta manera, hacemos nuestro código más modular: una vez que definimos la clase, podemos «olvidarnos» de esta y solo acudir a su código si queremos modificar algo específico.