Programación orientada a objetos y Pokémon

En programación orientada a objetos existen 4 conceptos esenciales que debemos dominar Abstracción, encapsulamiento, herencia y polimorfismo.

Leonidas Esteban

Leonidas Esteban

Programación orientada a objetos y Pokémon

En programación orientada a objetos existen 4 conceptos esenciales que debemos dominar: Abstracción, encapsulamiento, herencia y polimorfismo.

En este artículo te voy a enseñar estos conceptos usando Pokemon como referencia:

Batalla Pokemon en JavaScriptBatalla Pokemon en JavaScript

Abstración

La abstracción es uno de los conceptos fundamentales de la programación orientada a objetos que se refiere a la creación de una representación simplificada de un objeto o concepto complejo. En términos de Pokémon, esto significa que podemos representar a un Pokémon con un conjunto de atributos y comportamientos relevantes, sin tener que preocuparnos por todos los detalles internos y complejos de su biología y habilidades.

En JavaScript, podemos implementar la abstracción usando clases y objetos. Por ejemplo:

class Pokemon {
  constructor(name, type, level) {
    this.name = name;
    this.type = type;
    this.level = level;
  }

  attack(move) {
    console.log(`${this.name} used ${move}!`);
  }
}

const Pikachu = new Pokemon("Pikachu", "Electric", 10);
Pikachu.attack("Thunderbolt");
// Pikachu used Thunderbolt!

Encapsulamiento

El encapsulamiento es uno de los conceptos fundamentales de la programación orientada a objetos que se refiere a la protección de los atributos y comportamientos de un objeto para que solo sean accesibles y modificables a través de las interfaces públicas definidas. En términos de Pokémon, esto significa que podemos ocultar detalles internos de un Pokémon, como su nivel de vida y estado, y permitir que solo sean accesibles a través de métodos públicos como attack().

En JavaScript, podemos implementar el encapsulamiento usando la convención de nombres con # para identificar los métodos y atributos privados de un objeto. Por ejemplo:

class Pokemon {
  #hp = 100;

  constructor(name, type, level) {
    this.name = name;
    this.type = type;
    this.level = level;
  }

  #takeDamage(damage) {
    this.#hp -= damage;
    console.log(`${this.name} took ${damage} damage!`);
  }

  receiveAttack(move) {
    console.log(`${this.name} receive ${move}!`);
    this.#takeDamage(10);
 }


  attack(move) {
    console.log(`${this.name} used ${move}!`);
  }
}

const Pikachu = new Pokemon("Pikachu", "Electric", 10);
Pikachu.receiveAttack("Fireblast");
// Pikachu receive Fireblast!
// Pikachu took 10 damage!


En este ejemplo, utilizamos la convención de nombres con # para identificar los atributos y métodos privados de un objeto. El atributo #hp representa el nivel de vida de un Pokémon y solo puede ser modificado a través del método privado #takeDamage(). El método público attack() permite que los objetos interactúen con un Pokémon de manera segura y controlada, sin tener acceso a sus detalles internos.

De esta manera, el encapsulamiento nos permite proteger los atributos y comportamientos de un objeto, lo que mejora la seguridad y el mantenimiento del código.

Herencia

La herencia es uno de los conceptos clave de la programación orientada a objetos que permite crear nuevas clases a partir de clases existentes. Esto significa que una nueva clase puede heredar los atributos y comportamientos de una clase base y agregar o reemplazar comportamientos para adaptarse a sus necesidades.

En términos de Pokémon, esto significa que podemos crear una nueva clase FirePokemon que herede los atributos y comportamientos de una clase base Pokemon, y agregar o reemplazar comportamientos específicos para adaptarse a los Pokémon de tipo fuego.

En JavaScript, podemos implementar la herencia utilizando la palabra clave extends. Por ejemplo:

class Pokemon {
  constructor(name, type) {
    this.name = name;
    this.type = type;
  }

  attack() {
    console.log(`${this.name} used a basic attack!`);
  }
}

class FirePokemon extends Pokemon {
  constructor(name) {
    super(name, "Fire");
  }

  attack() {
    console.log(`${this.name} used a fiery attack!`);
  }
}

const Charmander = new FirePokemon("Charmander");
Charmander.attack();
// Charmander used a fiery attack!

En este ejemplo, la clase FirePokemon hereda los atributos y comportamientos de la clase base Pokemon, y reemplaza el comportamiento de attack() para adaptarse a los Pokémon de tipo fuego. La llamada a super en el constructor de FirePokemon permite que la clase base Pokemon sea construida primero y sus atributos sean inicializados.

De esta manera, la herencia nos permite reutilizar código y crear clases más especializadas que comparten comportamientos comunes con clases más generales.

Polimorfismo

El polimorfismo es otro concepto clave de la programación orientada a objetos que permite tratar objetos de diferentes clases de manera uniforme. En términos simples, significa que podemos usar una misma interfaz para diferentes tipos de objetos.

En términos de Pokémon, esto significa que podemos tener una clase base Pokemon con un método attack(), y luego crear diferentes clases especializadas para los diferentes tipos de Pokémon, como FirePokemon, WaterPokemon, etc., que sobrescriban el método attack() para adaptarse a sus habilidades únicas.

A continuación se muestra un ejemplo de polimorfismo en JavaScript:

class Pokemon {
  constructor(name, type) {
    this.name = name;
    this.type = type;
  }

  attack() {
    console.log(`${this.name} used a basic attack!`);
  }
}

class FirePokemon extends Pokemon {
  constructor(name) {
    super(name, "Fire");
  }

  attack() {
    console.log(`${this.name} used a fiery attack!`);
  }
}

class WaterPokemon extends Pokemon {
  constructor(name) {
    super(name, "Water");
  }

  attack() {
    console.log(`${this.name} used a watery attack!`);
  }
}

const Pikachu = new Pokemon("Pikachu", "Electric");
const Charmander = new FirePokemon("Charmander");
const Squirtle = new WaterPokemon("Squirtle");

const pokemons = [Pikachu, Charmander, Squirtle];

for (const pokemon of pokemons) {
  pokemon.attack();
  // Pikachu used a basic attack!
  // Charmander used a fiery attack!
  // Squirtle used a watery attack!
}

En este ejemplo, podemos ver que aunque los objetos Pikachu, Charmander y Squirtle son de diferentes clases, podemos tratarlos de manera uniforme llamando al método attack() en cada uno. Debido a la herencia y la sobrescritura de métodos, cada objeto invocará su propia implementación de attack(), resultando en comportamientos diferentes para cada objeto.

De esta manera, el polimorfismo nos permite escribir código más genérico que funcione con objetos de diferentes clases, lo que a su vez hace que nuestro código sea más flexible y reusable.

Ejercicio final

Apliquemos todos los conceptos en una batalla épica de Pikachu con Articuno (mi Pokemon favorito)

Charizard vs Articuno (no encontré de Pikachu)Charizard vs Articuno (no encontré de Pikachu)
class Pokemon {
  constructor(nombre, vida, ataque) {
    this.nombre = nombre;
    this.vida = vida;
    this.ataque = ataque;
  }

  atacar(pokemon) {
    pokemon.vida -= this.ataque;
  }
}

class Pikachu extends Pokemon {
  constructor(vida, ataque) {
    super("Pikachu", vida, ataque);
  }

  rayo() {
    this.ataque *= 2;
  }
}

class Articuno extends Pokemon {
  constructor(vida, ataque) {
    super("Articuno", vida, ataque);
  }

  hielo() {
    this.ataque *= 3;
  }
}

const pikachu = new Pikachu(100, 20);
const articuno = new Articuno(120, 15);

// Polimorfismo
pikachu.atacar(articuno);
articuno.atacar(pikachu);
pikachu.rayo();
articuno.hielo();
pikachu.atacar(articuno);
articuno.atacar(pikachu);

console.log(pikachu.vida); // 40
console.log(articuno.vida); // 60

En este ejemplo, la clase Pokemon es una clase abstracta que contiene los atributos y métodos básicos de un Pokémon, como su nombre, vida y ataque. Las clases Pikachu y Articuno heredan de la clase Pokemon y encapsulan sus atributos y métodos para agregar comportamientos específicos, como el método rayo en el caso de Pikachu y el método hielo en el caso de Articuno. En la batalla, los objetos pikachu y articuno son instancias de sus respectivas clases y pueden llamar a los métodos heredados y propios para atacar y modificar sus atributos. Esto es un ejemplo de polimorfismo, ya que ambos Pokémon pueden llamar al mismo método atacar pero tienen efectos diferentes en cada objeto.

Espero te haya gustado aprender a programar con Pokémon, visita nuestros cursos y proyectos para seguir nutriendo tus conocimientos y crear una excelente carrera en programación.

Ilustracion que representa como crece alguien profesionalmente

Entérate de las últimas novedades

Streamings, Noticias y Early Adopter bonus. Sé el primero en enterarte de todo.