🧱 Encapsulación, Composición, Polimorfismo y Abstracción en Java
📚 Índice
- 🔐 Encapsulación
- 🧠 Abstracción
- 🧩 Composición
- 🎭 Polimorfismo
- 🚀 Ejemplo completo integrando TODO (GymPro)
🔐 1. Encapsulación
🧠 ¿Qué es la encapsulación?
La encapsulación es uno de los pilares fundamentales de la POO.
Consiste en ocultar los detalles internos de una clase y permitir el acceso únicamente mediante métodos controlados.
👉 Idea clave: > "Los datos no deben modificarse directamente desde fuera de la clase."
Restringir el acceso desde fuera al funcionamiento interno de una clase, es decir, ocultar el trabajo interno que se realiza en una clase
🎯 Objetivos
- 🔒 Proteger los datos
- 🧹 Mantener coherencia del objeto
- 🛠 Controlar modificaciones
- 🧩 Facilitar mantenimiento
❌ Sin encapsulación
public class Persona {
public String nombre;
public int edad;
}
//Desde otra clase
Persona persona = new Persona(...);
persona.edad = -5; // ❌ Dato inválido
✅ Con encapsulación
public class Persona {
private String nombre;
private int edad;
public Persona(String nombre, int edad) {
setNombre(nombre);
setEdad(edad);
}
public String getNombre() { return nombre; }
public void setNombre(String nombre) {
if (nombre == null || nombre.isEmpty())
throw new IllegalArgumentException("Nombre inválido");
this.nombre = nombre;
}
public int getEdad() { return edad; }
public void setEdad(int edad) {
if (edad < 0)
throw new IllegalArgumentException("Edad inválida");
this.edad = edad;
}
}
//Desde otra clase
Persona persona = new Persona(...);
persona.setEdad(-5); // Lanza excepción
🧠 2. Abstracción
La abstracción es otro de los pilares fundamentales de la programación orientada a objetos.
🧩 ¿Qué es?
La abstracción consiste en quedarse con lo esencial y ocultar los detalles de implementación. Es mostrar solo la funcionalidad al usuario.
👉 Pensar en el qué hace, no en el cómo lo hace.
🏗 En Java
En Java podemos tener tanto:
Clases abstractac abstract class
Una clase abstracta es una clase que NO se puede instanciar pero que puede ser el padre de otras clases. Esto es útil, por ejemplo, cuando tenemos un concepto abstracto o amplio como Vehículo o Animal pero en realidad los objetos reales serían tipos específicos como Coche, Avión, Perro, etc.
Las clases abstractas pueden opcionalmente contener métodos abstractos. También pueden contener métodos no abstractos, que serán heredados por los hijos.
Aunque no se puedan instanciar las clases abstractas también poseen constructores. La mayoría de las veces se utilizan cuando quieres realizar alguna inicialización de los campos de la clase abstracta antes de la instanciación de una clase hija.
Métodos abstractos
Un método abstracto no tiene cuerpo. (No tiene código). Solo se escribe la signatura del método con la palabra reservada abstract.
Una clase hija no abstracta heredará el método o los métodos abstractos del padre y deberá desarrollarlos e implementarlo como métodos no abstractos, es decir, con código. Si no lo hace debe declararse como abstracta.
Un hijo abstracto de un padre abstracto no tiene que definir métodos no abstractos para los métodos abstractas que hereda.
Esto significa que puede haber varios pasos entre una clase base abstracta y una clase secundaria que no es completamente abstracta.
abstract class Animal {
private String nombre;
public Animal(String nombre) {
this.nombre = nombre;
}
//primero métodos abstractos, luego métodos normales
public abstract void hacerSonido();
public void imprimirNombre() {
System.out.println(this.nombre);
}
}
No sabemos cómo suena un animal genérico, pero sabemos que todos hacen sonido.
No todo lo definido en una clase abstracta debe ser abstracto. Sin embargo, si una clase contiene incluso un método abstracto, entonces la clase en sí debe declararse abstracta.
public class Dog extends Animal {
public Dog(String nombre) {
super(nombre);
}
@Override
public void hacerSonido() {
System.out.println("Ruff ruff");
}
}
Warning
Cuando se invoca un método, es la clase del objeto (no de la variable) la que determina qué método se ejecuta.
🎯 ¿Para qué sirve?
- Definir comportamientos comunes
- Obligar a las clases hijas a implementar métodos
- Diseñar jerarquías más claras
🧩 3. Composición
La composición es un principio de diseño fundamental en POO moderna. Tiene mayor flexibilidad, menos acoplamiento.
🧠 ¿Qué es?
Relación basada en:
👉 "Tiene un" (has-a)
Ejemplos:
- Un coche tiene un motor
- Una reserva tiene un socio
- Un equipo tiene jugadores
📌 Ejemplo
class Motor {
private int potencia;
public Motor(int potencia) {
this.potencia = potencia;
}
public int getPotencia() {
return potencia;
}
}
class Coche {
private Motor motor;
public Coche(int potenciaMotor) {
this.motor = new Motor(potenciaMotor);
}
public int getPotenciaMotor() {
return motor.getPotencia();
}
}
🎭 4. Polimorfismo
El Polimorfismo es uno de los 4 pilares de la programación orientada a objetos (POO) junto con la Abstracción, Encapsulación y Herencia. Para entender que es el polimorfismo es muy importante tener bastante claro el concepto de la Herencia.
Permite escribir código flexible y extensible.
🧠 ¿Qué es?
Permite que un mismo método tenga distintos comportamientos según el objeto real.
Para que haya polimorfismo dinámico en Java necesitamos que:
- 👉 Haya herencia
- 👉 Haya un método en el padre
- 👉 La clase hija lo sobreescriba
- 👉 Se use una referencia del tipo padre
- 👉 Java decide en tiempo de ejecución qué método ejecutar
Eso es lo que genera “múltiples formas”.
📌 Sobreescritura
abstract class Envio {
private double pesoKg;
public Envio(double pesoKg) {
if (pesoKg <= 0) throw new IllegalArgumentException("Peso inválido");
this.pesoKg = pesoKg;
}
public double getPesoKg() {
return pesoKg;
}
// 🎭 Polimorfismo: cada envío calcula su coste
public abstract double coste();
}
class EnvioNormal extends Envio {
public EnvioNormal(double pesoKg) { super(pesoKg); }
@Override
public double coste() {
return 3 + getPesoKg() * 0.5; // base + extra por peso
}
}
class EnvioExpress extends Envio {
public EnvioExpress(double pesoKg) { super(pesoKg); }
@Override
public double coste() {
return 6 + getPesoKg() * 0.8; // más caro
}
}
🔥 Polimorfismo real
Aquí ocurre el polimorfismo:
public class MainEnvios {
public static void main(String[] args) {
Envio e1 = new EnvioNormal(4); // 4 kg
Envio e2 = new EnvioExpress(4); // 4 kg
Envio[] envios = { e1, e2 };
for (Envio e : envios) {
//en e.coste() ocurre el polimorfismo,
// En el for: llamas a e.coste() y Java ejecuta el método correcto según el objeto real (EnvioNormal o EnvioExpress).
System.out.println(e.getClass().getSimpleName() + " -> " + e.coste() + "€");
}
}
}
Warning
Importante: El polimorfismo en Java solo existe en métodos de instancia. 🔴 Los atributos o métodos estáticos nunca son polimórficos en Java.
En este ejemplo no hay polimorfismo porque son atributos no métodos, entonces se usa la referencia:
class Padre {
int x = 1;
}
class Hija extends Padre {
int x = 2;
}
public class Main {
public static void main(String[] args) {
Padre p = new Hija();
System.out.println(p.x); // Sigue siendo 1 porque los atributos no tienen polimorfismo!!!
}
}
En ejemplo si habría polimorfismo porque es sobre métodos de instancia:
class Padre {
int x = 1;
public int getX() {
return x;
}
}
class Hija extends Padre {
int x = 2;
@Override
public int getX() {
return x;
}
}
public class Main {
public static void main(String[] args) {
Padre p = new Hija();
System.out.println(p.getX());//imprime 2, polimorfismo dinámico
}
}
🚀 5. Ejemplo Completo: GymPro
Este ejemplo integra: 🔐 Encapsulación, 🧠 Abstracción, 🧩 Composición y 🎭 Polimorfismo
// =====================
// 🧠 + 🔐 Clase abstracta
// =====================
abstract class ClaseFitness {
private String nombre;
private double precioBase;
public ClaseFitness(String nombre, double precioBase) {
if (nombre == null || nombre.isBlank())
throw new IllegalArgumentException("Nombre inválido");
if (precioBase <= 0)
throw new IllegalArgumentException("Precio inválido");
this.nombre = nombre;
this.precioBase = precioBase;
}
public String getNombre() { return nombre; }
public double getPrecioBase() { return precioBase; }
public abstract double calcularPrecioFinal(int edad);
}
// =====================
// 🎭 Clases hijas
// =====================
class Fuerza extends ClaseFitness {
public Fuerza(String nombre, double precioBase) {
super(nombre, precioBase);
}
@Override
public double calcularPrecioFinal(int edad) {
return getPrecioBase() + 2;
}
}
class Cardio extends ClaseFitness {
public Cardio(String nombre, double precioBase) {
super(nombre, precioBase);
}
@Override
public double calcularPrecioFinal(int edad) {
return edad < 18 ? getPrecioBase() - 1 : getPrecioBase();
}
}
// =====================
// 🔐 Socio
// =====================
class Socio {
private String nombre;
private int edad;
public Socio(String nombre, int edad) {
if (edad < 0)
throw new IllegalArgumentException("Edad inválida");
this.nombre = nombre;
this.edad = edad;
}
public String getNombre() { return nombre; }
public int getEdad() { return edad; }
}
// =====================
// 🧩 Reserva (composición)
// =====================
class Reserva {
private Socio socio;
private ClaseFitness clase;
public Reserva(Socio socio, ClaseFitness clase) {
if (socio == null || clase == null)
throw new IllegalArgumentException("Datos inválidos");
this.socio = socio;
this.clase = clase;
}
public double precioReserva() {
// 🎭 Aquí ocurre el polimorfismo
return clase.calcularPrecioFinal(socio.getEdad());
}
}
// =====================
// 🧪 Main
// =====================
public class MainGymPro {
public static void main(String[] args) {
Socio ana = new Socio("Ana", 16);
ClaseFitness c1 = new Cardio("Spinning", 8);
ClaseFitness c2 = new Fuerza("CrossFit", 9);
Reserva r1 = new Reserva(ana, c1);
Reserva r2 = new Reserva(ana, c2);
System.out.println("Precio Spinning: " + r1.precioReserva());
System.out.println("Precio CrossFit: " + r2.precioReserva());
}
}
🎯 Resumen Final
| Concepto | Idea Clave |
|---|---|
| 🔐 Encapsulación | Proteger datos |
| 🧠 Abstracción | Definir comportamiento esencial |
| 🧩 Composición | Una clase contiene otra |
| 🎭 Polimorfismo | Mismo método, distinto comportamiento |
🏁 Conclusión
Si entiendes este ejemplo completo, ya estás trabajando con los pilares reales de la POO.