Programación orientada a objetos (POO)
La programación orientada a objetos es un paradigma de programación que se basa en el uso de objetos que interactúan entre sí para resolver un problema. Los objetos pueden tener propiedades y métodos que los definen, y se pueden crear nuevas instancias de un objeto a partir de una clase que lo define.
Ejemplo:
Supongamos que queremos crear una aplicación que permita almacenar información sobre estudiantes de una escuela. Cada estudiante tendrá un nombre, una edad y una nota promedio. Para ello, podemos crear una clase Estudiante que defina las propiedades y métodos que tendrá cada estudiante:
// Definir la clase Estudiante
class Estudiante {
// Constructor que toma nombre, edad y notaPromedio como argumentos
constructor(nombre, edad, notaPromedio) {
// Asignar los argumentos a las propiedades correspondientes
this.nombre = nombre;
this.edad = edad;
this.notaPromedio = notaPromedio;
}
// Método para obtener el nombre del estudiante
obtenerNombre() {
return this.nombre;
}
// Método para obtener la edad del estudiante
obtenerEdad() {
return this.edad;
}
// Método para obtener la nota promedio del estudiante
obtenerNotaPromedio() {
return this.notaPromedio;
}
// Método para establecer la nota promedio del estudiante
establecerNotaPromedio(notaPromedio) {
this.notaPromedio = notaPromedio;
}
}En esta clase, creamos un constructor que recibe los parámetros nombre, edad y notaPromedio y los asigna a las propiedades nombre, edad y notaPromedio de la instancia. Luego, creamos métodos que nos permiten obtener y establecer las propiedades de la instancia.
Para crear un nuevo estudiante, podemos crear una nueva instancia de la clase Estudiante y asignarle los valores correspondientes:
const juan = new Estudiante("Juan", 15, 8.5);En este ejemplo, creamos una instancia de la clase Estudiante con el nombre "Juan", la edad 15 y la nota promedio 8.5.
Luego, podemos utilizar los métodos de la instancia para obtener o establecer sus propiedades:
console.log(juan.obtenerNombre()); // Resultado: "Juan"
console.log(juan.obtenerEdad()); // Resultado: 15
console.log(juan.obtenerNotaPromedio()); // Resultado: 8.5
juan.establecerNotaPromedio(9);
console.log(juan.obtenerNotaPromedio()); // Resultado: 9En este ejemplo, utilizamos los métodos obtenerNombre, obtenerEdad y obtenerNotaPromedio para obtener las propiedades de la instancia juan. Luego, utilizamos el método establecerNotaPromedio para cambiar la nota promedio de juan y mostramos el resultado en la consola.
Con la programación orientada a objetos, podemos crear clases y objetos que nos permiten modelar situaciones de la vida real y resolver problemas de manera más eficiente y organizada.
Buenas prácticas
- Utiliza
constpara declarar variables que no cambiarán su valor yletpara las que sí lo harán. - Utiliza
classpara definir clases ynewpara crear nuevas instancias de una clase. - Utiliza
thispara referirte a la instancia actual de una clase. - Utiliza
constructorpara definir el constructor de una clase. - Utiliza
extendspara crear una clase que herede de otra clase. - Utiliza
superpara llamar al constructor de la clase padre. - Utiliza
extendspara crear una clase que herede de otra clase.
Declaración de clases
La declaración de clases en JavaScript se introdujo en ECMAScript 6 como una forma de definir objetos que contienen propiedades y métodos relacionados. Una clase es una plantilla que se utiliza para crear objetos y define cómo se deben crear y manipular esos objetos. Las clases pueden heredar propiedades y métodos de otras clases, lo que las hace muy útiles para modelar situaciones complejas.
Ejemplo:
Supongamos que queremos crear una clase Persona que defina las propiedades y métodos básicos de una persona, como su nombre y edad. Podemos crear una clase utilizando la sintaxis class de la siguiente manera:
// Definir la clase Persona
class Persona {
// Constructor que toma nombre y edad como argumentos
constructor(nombre, edad) {
// Asignar los argumentos a las propiedades correspondientes
this.nombre = nombre;
this.edad = edad;
}
// Método para presentarse
presentarse() {
// Imprimir un mensaje en la consola que incluye el nombre y la edad de la persona
console.log(`Hola, mi nombre es ${this.nombre} y tengo ${this.edad} años.`);
}
}En esta clase, creamos un constructor que recibe los parámetros nombre y edad y los asigna a las propiedades nombre y edad de la instancia. Luego, creamos un método presentarse que muestra un mensaje personalizado en la consola utilizando las propiedades nombre y edad.
Para crear una nueva instancia de la clase Persona, podemos utilizar la sintaxis new Persona:
// Crear una nueva instancia de la clase Persona con nombre "Juan" y edad 25
const juan = new Persona("Juan", 25);En este ejemplo, creamos una nueva instancia de la clase Persona con el nombre "Juan" y la edad 25.
Luego, podemos utilizar los métodos de la instancia para acceder y modificar sus propiedades:
console.log(juan.nombre); // Resultado: "Juan"
console.log(juan.edad); // Resultado: 25
juan.edad = 26;
console.log(juan.edad); // Resultado: 26
juan.presentarse(); // Resultado: "Hola, mi nombre es Juan y tengo 26 años."En este ejemplo, utilizamos la notación de punto para acceder a las propiedades nombre y edad de la instancia juan. Luego, utilizamos la misma notación para modificar la propiedad edad de juan y mostrar el resultado en la consola. Finalmente, llamamos al método presentarse de la instancia para mostrar un mensaje personalizado en la consola.
La declaración de clases en JavaScript nos permite crear objetos complejos con facilidad y organizar nuestro código de una manera clara y estructurada.
Buenas prácticas
- Las clases deben tener nombres en singular y en mayúscula.
- Las clases deben tener un constructor que reciba los parámetros necesarios para crear una nueva instancia.
- Las clases deben tener métodos que permitan acceder y modificar sus propiedades.
- Las clases deben tener métodos que permitan realizar acciones con sus propiedades.
- Las clases deben tener métodos que permitan acceder y modificar sus propiedades.
- Las clases deben tener métodos que permitan realizar acciones con sus propiedades.
Abstracción en clases
La abstracción en clases se refiere a la capacidad de una clase para definir propiedades y métodos que no están relacionados directamente con su funcionalidad principal. Esto puede ayudar a simplificar el código y hacerlo más fácil de mantener y modificar en el futuro. En JavaScript, la abstracción se puede lograr utilizando clases abstractas y herencia.
Ejemplo:
Supongamos que queremos crear una clase Animal que defina las propiedades y métodos básicos de un animal, como su especie y su hábitat. Podemos crear una clase abstracta Animal utilizando la sintaxis class y la palabra clave abstract de la siguiente manera:
// Definir la clase abstracta Animal
abstract class Animal {
// Constructor que toma especie y habitat como argumentos
constructor(especie, habitat) {
// Asignar los argumentos a las propiedades correspondientes
this.especie = especie;
this.habitat = habitat;
}
// Método abstracto hacerSonido, que debe ser implementado en subclases de Animal
abstract hacerSonido();
}En esta clase, creamos un constructor que recibe los parámetros especie y habitat y los asigna a las propiedades especie y habitat de la instancia. Luego, creamos un método abstracto hacerSonido que no tiene implementación. Esta es la abstracción: la clase define un comportamiento que no está relacionado directamente con su funcionalidad principal.
Podemos crear clases hijas que extiendan la clase abstracta Animal y definan la implementación del método hacerSonido. Por ejemplo, podemos crear una clase Perro que defina el sonido que hace un perro:
// Definir la subclase Perro de la clase Animal
class Perro extends Animal {
// Constructor que toma especie y habitat como argumentos y los pasa a super()
constructor(especie, habitat) {
super(especie, habitat);
}
// Implementar el método abstracto hacerSonido() de la clase Animal
hacerSonido() {
console.log("¡Guau!");
}
}En esta clase, utilizamos la palabra clave extends para indicar que Perro es una clase hija de Animal. Luego, creamos un constructor que llama al constructor de la clase Animal utilizando la palabra clave super. Finalmente, definimos la implementación del método hacerSonido para que muestre "¡Guau!" en la consola.
Podemos crear una nueva instancia de la clase Perro y llamar al método hacerSonido para verificar su implementación:
// Crear una nueva instancia de la subclase Perro con especie "Canis lupus familiaris" y hábitat "Doméstico"
const miPerro = new Perro("Canis lupus familiaris", "Doméstico");
// Llamar al método hacerSonido() en miPerro
miPerro.hacerSonido(); // Resultado: "¡Guau!"En este ejemplo, creamos una nueva instancia de la clase Perro con la especie "Canis lupus familiaris" y el hábitat "Doméstico". Luego, llamamos al método hacerSonido de la instancia para mostrar el mensaje "¡Guau!" en la consola.
La abstracción en clases nos permite definir comportamientos abstractos que no están directamente relacionados con la funcionalidad principal de una clase y crear clases hijas que extiendan esta funcionalidad para cumplir con requisitos específicos. Esto hace que nuestro código sea más modular y fácil de mantener en el futuro.
Herencia en clases
La herencia en JavaScript se refiere a la capacidad de una clase de heredar propiedades y métodos de otra clase. Esto nos permite definir una clase base que contenga la funcionalidad común a varias clases y luego crear clases hijas que extiendan esta funcionalidad para cumplir con requisitos específicos.
Ejemplo:
Supongamos que queremos crear una clase Persona que defina las propiedades y métodos básicos de una persona, como su nombre y edad. Podemos crear una clase Persona utilizando la sintaxis class de la siguiente manera:
// Definir la clase Persona
class Persona {
// Constructor que toma nombre y edad como argumentos
constructor(nombre, edad) {
// Asignar los argumentos a las propiedades correspondientes
this.nombre = nombre;
this.edad = edad;
}
// Método para presentarse
presentarse() {
// Imprimir un mensaje en la consola que incluye el nombre y la edad de la persona
console.log(`Hola, mi nombre es ${this.nombre} y tengo ${this.edad} años.`);
}
}En esta clase, creamos un constructor que recibe los parámetros nombre y edad y los asigna a las propiedades nombre y edad de la instancia. Luego, creamos un método presentarse que muestra un mensaje personalizado en la consola utilizando las propiedades nombre y edad.
Podemos crear una nueva instancia de la clase Persona y llamar al método presentarse para verificar su implementación:
// Crear una nueva instancia de la clase Persona con nombre "Juan" y edad 25
const juan = new Persona("Juan", 25);
// Llamar al método presentarse() en juan
juan.presentarse(); // Resultado: "Hola, mi nombre es Juan y tengo 25 años."En este ejemplo, creamos una nueva instancia de la clase Persona con el nombre "Juan" y la edad 25. Luego, llamamos al método presentarse de la instancia para mostrar el mensaje personalizado en la consola.
Supongamos ahora que queremos crear una clase Estudiante que extienda la clase Persona y defina una propiedad adicional, como su nota promedio. Podemos crear una clase hija Estudiante utilizando la sintaxis extends de la siguiente manera:
// Definir la subclase Estudiante de la clase Persona
class Estudiante extends Persona {
// Constructor que toma nombre, edad y notaPromedio como argumentos y los pasa a super()
constructor(nombre, edad, notaPromedio) {
super(nombre, edad);
this.notaPromedio = notaPromedio;
}
// Método para mostrar la nota promedio del estudiante
mostrarNota() {
console.log(`Mi nota promedio es ${this.notaPromedio}.`);
}
}En esta clase, utilizamos la palabra clave extends para indicar que Estudiante es una clase hija de Persona. Luego, creamos un constructor que llama al constructor de la clase Persona utilizando la palabra clave super y asigna la propiedad notaPromedio a la instancia. Finalmente, creamos un método mostrarNota que muestra un mensaje personalizado en la consola utilizando la propiedad notaPromedio.
Podemos crear una nueva instancia de la clase Estudiante y llamar a los métodos de ambas clases para verificar su implementación:
// Crear una nueva instancia de la subclase Estudiante con nombre "Maria", edad 20 y nota promedio 9.5
const maria = new Estudiante("Maria", 20, 9.5);
// Llamar al método presentarse() en maria
maria.presentarse(); // Resultado: "Hola, mi nombre es Maria y tengo 20 años."
// Llamar al método mostrarNota() en maria
maria.mostrarNota(); // Resultado: "Mi nota promedio es 9.5."En este ejemplo, creamos una nueva instancia de la clase Estudiante con el nombre "Maria", la edad 20 y la nota promedio 9.5. Luego, llamamos a los métodos presentarse y mostrarNota de la instancia para mostrar los mensajes personalizados en la consola.
Buenas prácticas
- Utilizar la palabra clave
extendspara definir una clase hija. - Utilizar la palabra clave
superpara llamar al constructor de la clase padre. - Utilizar la palabra clave
superpara llamar a los métodos de la clase padre.
Encapsulamiento en clases
El encapsulamiento en JavaScript se refiere a la capacidad de una clase de ocultar sus propiedades y métodos internos y exponer solo los que se necesitan externamente. Esto puede ayudar a proteger la integridad del objeto y evitar que se acceda y modifique sus propiedades y métodos de manera inadecuada. En JavaScript, el encapsulamiento se puede lograr utilizando los modificadores de acceso public, private y protected.
Ejemplo:
Supongamos que queremos crear una clase CuentaBancaria que defina las propiedades y métodos básicos de una cuenta bancaria, como su saldo y su número de cuenta. Sin embargo, queremos proteger el saldo de la cuenta de ser accedido y modificado directamente. Podemos crear una clase CuentaBancaria utilizando la sintaxis class y la palabra clave private para proteger el saldo de la cuenta:
// Definir la clase CuentaBancaria
class CuentaBancaria {
#saldo = 0;
// Constructor que toma numeroCuenta como argumento
constructor(numeroCuenta) {
// Asignar el argumento a la propiedad numeroCuenta
this.numeroCuenta = numeroCuenta;
}
// Getter para la propiedad privada #saldo
get saldo() {
return this.#saldo;
}
// Método para depositar una cantidad en la cuenta
depositar(cantidad) {
this.#saldo += cantidad;
console.log(`Se han depositado $${cantidad} en la cuenta.`);
}
// Método para retirar una cantidad de la cuenta
retirar(cantidad) {
if (cantidad > this.#saldo) {
console.log("No hay suficiente saldo para realizar la operación.");
} else {
this.#saldo -= cantidad;
console.log(`Se han retirado $${cantidad} de la cuenta.`);
}
}
}En esta clase, creamos una propiedad #saldo utilizando la sintaxis #nombrePropiedad y la palabra clave private.Esto hace que la propiedad #saldo sea inaccesible desde fuera de la clase. Luego, creamos un método get llamado saldoque devuelve el valor de la propiedad #saldo.
También creamos dos métodos depositar y retirar que modifican la propiedad #saldo de la instancia. Si la cantidad a retirar es mayor que el saldo disponible, se muestra un mensaje de error en la consola.
Para crear una nueva instancia de la clase CuentaBancaria, podemos utilizar la sintaxis new CuentaBancaria:
// Crear una nueva instancia de la clase CuentaBancaria con numeroCuenta "123456789"
const miCuenta = new CuentaBancaria("123456789");En este ejemplo, creamos una nueva instancia de la clase CuentaBancaria con el número de cuenta "123456789".
Podemos utilizar los métodos de la instancia para depositar y retirar fondos:
// Depositar 1000 en miCuenta
miCuenta.depositar(1000); // Resultado: "Se han depositado $1000 en la cuenta."
// Mostrar el saldo actualizado de miCuenta
console.log(miCuenta.saldo); // Resultado: 1000
// Retirar 500 de miCuenta
miCuenta.retirar(500); // Resultado: "Se han retirado $500 de la cuenta."
// Mostrar el saldo actualizado de miCuenta
console.log(miCuenta.saldo); // Resultado: 500
// Intentar retirar 1000 de miCuenta, que no es posible debido al saldo insuficiente
miCuenta.retirar(1000); // Resultado: "No hay suficiente saldo para realizar la operación."En este ejemplo, utilizamos los métodos depositar y retirar de la instancia para modificar su propiedad #saldo. Luego, utilizamos el método get para obtener el valor actual del saldo y lo mostramos en la consola. Finalmente, intentamos retirar más dinero del que hay disponible en la cuenta y se muestra un mensaje de error en la consola.
El encapsulamiento en JavaScript nos permite proteger las propiedades y métodos internos de una clase y exponer solo los que necesitamos externamente. Esto ayuda a mantener la integridad del objeto y evitar que se acceda y modifique sus propiedades y métodos de manera inadecuada.
Buenas prácticas
- Utiliza la palabra clave
privatepara proteger las propiedades y métodos internos de una clase. - Utiliza la palabra clave
publicpara exponer las propiedades y métodos externamente. - Utiliza la palabra clave
protectedpara exponer las propiedades y métodos a las clases hijas.
Polimorfismo en clases
El polimorfismo en JavaScript se refiere a la capacidad de un objeto de tomar varias formas y comportarse de manera diferente según el contexto en el que se utilice. Esto nos permite escribir código más flexible y reutilizable, ya que un objeto puede ser utilizado en diferentes contextos sin necesidad de conocer su tipo específico.
Ejemplo:
Supongamos que queremos crear una clase Figura que defina las propiedades y métodos básicos de una figura geométrica, como su área y su perímetro. Podemos crear una clase Figura utilizando la sintaxis class de la siguiente manera:
// Definir la clase Figura
class Figura {
constructor() {}
// Métodos abstractos que deben ser implementados por cualquier subclase
calcularArea() {}
calcularPerimetro() {}
}En esta clase, creamos un constructor vacío y dos métodos calcularArea y calcularPerimetro que no tienen implementación.
Podemos crear dos clases hijas Rectangulo y Circulo que extiendan la clase Figura y definan la implementación de estos métodos para calcular el área y el perímetro de un rectángulo y un círculo respectivamente:
// Definir la subclase Rectangulo de la clase Figura
class Rectangulo extends Figura {
constructor(base, altura) {
super();
this.base = base;
this.altura = altura;
}
calcularArea() {
return this.base * this.altura;
}
calcularPerimetro() {
return 2 * this.base + 2 * this.altura;
}
}
// Definir la subclase Circulo de la clase Figura
class Circulo extends Figura {
constructor(radio) {
super();
this.radio = radio;
}
calcularArea() {
return Math.PI * this.radio ** 2;
}
calcularPerimetro() {
return 2 * Math.PI * this.radio;
}
}En estas clases, utilizamos la palabra clave extends para indicar que Rectangulo y Circulo son clases hijas de Figura. Luego, creamos un constructor en cada clase que llama al constructor de la clase Figura utilizando la palabra clave super y asigna las propiedades necesarias a la instancia.
Finalmente, definimos la implementación de los métodos calcularArea y calcularPerimetro de cada clase para calcular el área y el perímetro de un rectángulo o un círculo respectivamente.
Podemos crear una instancia de cada clase y llamar a sus métodos para verificar su implementación:
// Crear una instancia de la subclase Rectangulo
const miRectangulo = new Rectangulo(5, 10);
// Mostrar el área y el perímetro de miRectangulo
console.log(miRectangulo.calcularArea()); // Resultado: 50
console.log(miRectangulo.calcularPerimetro()); // Resultado: 30
// Crear una instancia de la subclase Circulo
const miCirculo = new Circulo(4);
// Mostrar el área y el perímetro de miCirculo
console.log(miCirculo.calcularArea()); // Resultado: 50.26548245743669
console.log(miCirculo.calcularPerimetro()); // Resultado: 25.132741228718345En este ejemplo, creamos una instancia de la clase Rectangulo con una base de 5 y una altura de 10. Luego, llamamos a los métodos calcularArea y calcularPerimetro de la instancia para mostrar el área y el perímetro del rectángulo en la consola.
También creamos una instancia de la clase Circulo con un radio de 4 y llamamos a los métodos calcularArea y calcularPerimetro de la instancia para mostrar el área y el perímetro del círculo en la consola.
En este ejemplo, utilizamos el polimorfismo para crear dos clases hijas que extienden la funcionalidad de la clase Figura y definen su propia implementación de los métodos calcularArea y calcularPerimetro. Esto nos permite utilizar estas clases hijas en diferentes contextos, como en una aplicación de dibujo o en un programa de cálculo de geometría, sin necesidad de conocer su tipo específico.
Buenas prácticas
- Utiliza el polimorfismo para crear clases hijas que extiendan la funcionalidad de una clase padre.
- Utiliza el polimorfismo para crear clases hijas que implementen su propia versión de los métodos de una clase padre.