🌐 ¿Qué es la Programación Orientada a Objetos?
La Programación Orientada a Objetos (POO, o OOP en inglés — Object-Oriented Programming) es un paradigma de programación que organiza el software en torno a objetos en lugar de funciones y lógica secuencial. Cada objeto combina datos (llamados atributos o campos) y comportamientos (llamados métodos) en una única entidad cohesiva.
La idea central es sencilla pero poderosa: en lugar de pensar en un programa como una secuencia de instrucciones que manipulan datos sueltos, pensamos en él como un conjunto de objetos que colaboran entre sí enviándose mensajes. Este enfoque refleja la forma en que percibimos el mundo real, donde las entidades tienen propiedades y realizan acciones.
📘 Definición formal: La POO es un paradigma de programación basado en el concepto de objetos, que contienen datos en forma de campos (atributos) y código en forma de procedimientos (métodos). Los objetos interactúan entre sí mediante el envío de mensajes (invocaciones de métodos).
Java es uno de los lenguajes de programación orientados a objetos más utilizados en el mundo. Desde su creación en 1995, fue diseñado con la POO como filosofía central: en Java, ✅ todo el código se organiza en clases, y la unidad básica de trabajo es el objeto.
| Concepto | Descripción | Analogía mundo real |
|---|---|---|
| Clase | Plantilla que define atributos y métodos | Plano de un edificio |
| Objeto | Instancia concreta de una clase | El edificio construido |
| Atributo | Variable que almacena el estado del objeto | Color, número de pisos |
| Método | Función que define el comportamiento del objeto | Abrir la puerta, encender luces |
| Mensaje | Invocación de un método sobre un objeto | Pedirle al portero que abra |
📜 Breve historia de la POO
La evolución de los paradigmas de programación ha seguido una tendencia constante: descomponer los problemas en unidades cada vez más manejables. Comprender esta evolución ayuda a entender por qué la POO se convirtió en el paradigma dominante.
▶️ Programación lineal (años 50-60)
Los primeros programas eran secuencias de instrucciones ejecutadas una tras otra, sin estructura. Los lenguajes como FORTRAN y COBOL producían código difícil de mantener cuando los programas crecían — lo que se conocía como código espagueti por la maraña de saltos (GOTO) que hacían el flujo prácticamente imposible de seguir.
🔹 Programación estructurada (años 70)
La programación estructurada, popularizada por lenguajes como Pascal y C, introdujo la descomposición en funciones y procedimientos. El teorema de Böhm-Jacopini demostró que cualquier programa puede escribirse usando solo tres estructuras: secuencia, selección e iteración. Esto eliminó el GOTO y produjo código más legible, pero los datos y las funciones seguían siendo entidades separadas.
🔸 Nacimiento de la POO (años 60-80)
El concepto de objeto apareció por primera vez en Simula 67 (1967), creado por Ole-Johan Dahl y Kristen Nygaard en Noruega para simulaciones. En los años 70, Alan Kay desarrolló Smalltalk en Xerox PARC, el primer lenguaje puramente orientado a objetos, acuñando el término «programación orientada a objetos». En 1985, Bjarne Stroustrup creó C++ extendiendo C con clases y herencia, llevando la POO al mundo de la programación de sistemas.
➡️ Consolidación: Java y más allá (1995-hoy)
En 1995, James Gosling y su equipo en Sun Microsystems lanzaron Java, que combinaba la POO con la portabilidad de la JVM (Java Virtual Machine). Java simplificó C++ eliminando la gestión manual de memoria y la herencia múltiple de clases, ofreciendo un modelo más seguro. Hoy, la POO es el paradigma dominante: lenguajes como Java, C#, Python, JavaScript, Kotlin, Swift y TypeScript utilizan sus principios.
💡 Dato: Alan Kay, creador de Smalltalk, definió la POO así: «Todo es un objeto. Los objetos se comunican enviando y recibiendo mensajes. Los objetos tienen su propia memoria».
🏛️ Los cuatro pilares de la POO
La Programación Orientada a Objetos se sustenta sobre cuatro principios fundamentales, conocidos como los «pilares» de la POO. Estos principios no son independientes entre sí: trabajan de forma coordinada para producir software modular, reutilizable y mantenible.
| Pilar | ¿Qué hace? | Beneficio principal |
|---|---|---|
| 🛡️ Encapsulamiento | Oculta los detalles internos del objeto | Protección y control del estado |
| 🧬 Herencia | Permite que una clase extienda a otra | Reutilización de código |
| 🎭 Polimorfismo | Un mismo método se comporta de forma diferente | Flexibilidad y extensibilidad |
| 🔮 Abstracción | Modela solo las características esenciales | Simplicidad y enfoque |
En las siguientes secciones desarrollaremos cada pilar en detalle con ejemplos en Java. Antes, necesitamos comprender los conceptos de clase y objeto, que son la base sobre la que se construyen los cuatro pilares.
📦 Clases y objetos en Java
▶️ ¿Qué es una clase?
Una clase es una plantilla o molde que define la estructura y el comportamiento que tendrán los objetos creados a partir de ella. La clase especifica qué atributos (datos) tendrá cada objeto y qué métodos (acciones) podrá realizar. En Java, toda la lógica del programa se organiza dentro de clases.
public class Automovil {
// Atributos (estado del objeto)
String marca;
String modelo;
String color;
int velocidadActual;
// Constructor
public Automovil(String marca, String modelo, String color) {
this.marca = marca;
this.modelo = modelo;
this.color = color;
this.velocidadActual = 0;
}
// Métodos (comportamiento del objeto)
public void acelerar(int incremento) {
this.velocidadActual += incremento;
System.out.println(marca + " " + modelo + " acelera a " + velocidadActual + " km/h");
}
public void frenar() {
this.velocidadActual = 0;
System.out.println(marca + " " + modelo + " se detiene");
}
}
🔹 ¿Qué es un objeto?
Un objeto es una instancia concreta de una clase. Mientras la clase es el molde, el objeto es la pieza fabricada a partir de ese molde, con valores específicos en sus atributos. A partir de una misma clase se pueden crear múltiples objetos, cada uno con su propio estado independiente.
public class Main {
public static void main(String[] args) {
// Crear dos objetos a partir de la misma clase
Automovil coche1 = new Automovil("Ford", "Focus", "Azul");
Automovil coche2 = new Automovil("Toyota", "Corolla", "Rojo");
// Cada objeto tiene su propio estado
coche1.acelerar(80); // Ford Focus acelera a 80 km/h
coche2.acelerar(60); // Toyota Corolla acelera a 60 km/h
coche1.frenar(); // Ford Focus se detiene
// coche2 sigue a 60 km/h — estados independientes
}
}
📘 Terminología: «Crear un objeto», «instanciar una clase» y «construir una instancia» son expresiones equivalentes. El operador new en Java invoca al constructor de la clase y reserva memoria para el nuevo objeto en el heap.
🔸 Componentes de un objeto
Todo objeto tiene tres componentes fundamentales:
Estado: los valores actuales de sus atributos. Por ejemplo, un automóvil con marca="Ford", color="Azul" y velocidadActual=80.
Comportamiento: las acciones que puede realizar, definidas por sus métodos. Por ejemplo, acelerar(), frenar().
Identidad: cada objeto es único, incluso si tiene el mismo estado que otro. Java distingue dos objetos diferentes aunque sus atributos sean idénticos porque ocupan posiciones distintas en memoria.
🛡️ Encapsulamiento
El encapsulamiento es el principio que consiste en agrupar datos y métodos dentro de una clase y restringir el acceso directo a los datos internos del objeto. En lugar de permitir que cualquier parte del programa modifique los atributos directamente, el encapsulamiento obliga a usar métodos controlados (llamados getters y setters) que pueden incluir validaciones.
En Java, el encapsulamiento se implementa mediante los modificadores de acceso:
| Modificador | Clase | Paquete | Subclase | Mundo |
|---|---|---|---|---|
private |
✅ | ❌ | ❌ | ❌ |
default (sin modificador) |
✅ | ✅ | ❌ | ❌ |
protected |
✅ | ✅ | ✅ | ❌ |
public |
✅ | ✅ | ✅ | ✅ |
public class CuentaBancaria {
private String titular;
private double saldo;
public CuentaBancaria(String titular, double saldoInicial) {
this.titular = titular;
this.saldo = saldoInicial;
}
// Getter: acceso controlado al saldo
public double getSaldo() {
return saldo;
}
// Setter con validación: no permite depósitos negativos
public void depositar(double cantidad) {
if (cantidad <= 0) {
System.out.println("⚠️ La cantidad debe ser positiva");
return;
}
saldo += cantidad;
}
// Método con lógica de negocio
public boolean retirar(double cantidad) {
if (cantidad <= 0 || cantidad > saldo) {
System.out.println("⚠️ Retiro inválido");
return false;
}
saldo -= cantidad;
return true;
}
}
💡 Buena práctica: Declara siempre los atributos como private y proporciona métodos public para acceder y modificar el estado. Esto permite añadir validaciones, registros de auditoría o notificaciones sin que el código cliente se vea afectado.
El encapsulamiento también incluye el concepto de ocultamiento de información (information hiding): los detalles de implementación interna de una clase quedan ocultos al exterior. El usuario de la clase solo necesita conocer la interfaz pública (los métodos disponibles), no cómo funcionan internamente. Esto convierte cada clase en una ✅ «caja negra» que expone solo lo necesario.
🧬 Herencia
La herencia es el mecanismo por el cual una clase (subclase o clase hija) puede heredar atributos y métodos de otra clase (superclase o clase padre). La subclase reutiliza el código de la superclase y puede añadir nuevos atributos o métodos, o modificar (sobrescribir) los heredados.
En Java, la herencia se declara con la palabra reservada extends. Una clase solo puede heredar de una única superclase (herencia simple), aunque puede implementar múltiples interfaces.
// Superclase
public class Animal {
protected String nombre;
protected int edad;
public Animal(String nombre, int edad) {
this.nombre = nombre;
this.edad = edad;
}
public void comer() {
System.out.println(nombre + " está comiendo");
}
public String toString() {
return nombre + " (" + edad + " años)";
}
}
// Subclase — hereda de Animal
public class Perro extends Animal {
private String raza;
public Perro(String nombre, int edad, String raza) {
super(nombre, edad); // Llama al constructor de Animal
this.raza = raza;
}
// Método propio de Perro
public void ladrar() {
System.out.println(nombre + " dice: ¡Guau!");
}
// Sobrescritura del método heredado
@Override
public String toString() {
return nombre + " (" + raza + ", " + edad + " años)";
}
}
🔹 Jerarquía de clases
La herencia permite crear jerarquías de clases donde las clases más generales están arriba y las más especializadas abajo. En Java, todas las clases heredan implícitamente de java.lang.Object, que es la raíz de toda la jerarquía.
⚠️ Precaución: No abuses de la herencia. Si la relación entre dos clases no es claramente «es un/a» (un Perro es un Animal), considera usar composición en lugar de herencia. La regla es: hereda comportamiento, no solo para reutilizar código.
🎭 Polimorfismo
El polimorfismo (del griego poly = muchos, morphé = forma) es la capacidad de un objeto de adoptar múltiples formas. En la práctica, significa que un mismo método puede comportarse de manera diferente según el objeto que lo invoque.
Java implementa dos tipos de polimorfismo:
▶️ Polimorfismo en tiempo de compilación (sobrecarga)
Varios métodos en la misma clase comparten el mismo nombre pero difieren en sus parámetros (tipo, número u orden). El compilador determina cuál invocar basándose en los argumentos.
public class Calculadora {
public int sumar(int a, int b) {
return a + b;
}
public double sumar(double a, double b) {
return a + b;
}
public int sumar(int a, int b, int c) {
return a + b + c;
}
}
🔸 Polimorfismo en tiempo de ejecución (sobrescritura)
Una subclase proporciona su propia implementación de un método heredado de la superclase. La JVM determina en tiempo de ejecución qué versión del método invocar, basándose en el tipo real del objeto (no en el tipo de la referencia).
public class Figura {
public double calcularArea() {
return 0;
}
}
public class Circulo extends Figura {
private double radio;
public Circulo(double radio) { this.radio = radio; }
@Override
public double calcularArea() {
return Math.PI * radio * radio;
}
}
public class Rectangulo extends Figura {
private double ancho, alto;
public Rectangulo(double ancho, double alto) {
this.ancho = ancho;
this.alto = alto;
}
@Override
public double calcularArea() {
return ancho * alto;
}
}
// Uso polimórfico
public class Main {
public static void main(String[] args) {
Figura[] figuras = {
new Circulo(5),
new Rectangulo(4, 6),
new Circulo(3)
};
for (Figura f : figuras) {
// La JVM llama al método correcto según el tipo real
System.out.println("Área: " + f.calcularArea());
}
}
}
💡 Clave: El polimorfismo permite escribir código genérico que trabaja con la superclase, pero ejecuta el comportamiento específico de cada subclase. Esto es la base de los principios SOLID y del diseño de frameworks profesionales.
🔮 Abstracción
La abstracción es el proceso de identificar las características esenciales de un objeto e ignorar los detalles irrelevantes para el contexto del problema. En programación, abstraer significa crear modelos simplificados de entidades complejas del mundo real, capturando solo lo que importa para el sistema que estamos construyendo.
Java implementa la abstracción mediante dos mecanismos:
▶️ Clases abstractas
Una clase abstracta es una clase que ❌ no puede instanciarse directamente. Puede contener métodos abstractos (sin implementación) que las subclases están obligadas a implementar, y también métodos concretos con implementación completa.
public abstract class Vehiculo {
protected String matricula;
public Vehiculo(String matricula) {
this.matricula = matricula;
}
// Método abstracto: cada subclase define cómo arranca
public abstract void arrancar();
// Método concreto: compartido por todas las subclases
public void mostrarMatricula() {
System.out.println("Matrícula: " + matricula);
}
}
🔹 Interfaces
Una interfaz define un contrato: un conjunto de métodos que la clase que la implemente debe proporcionar. A diferencia de las clases, una clase puede implementar múltiples interfaces, lo que permite una forma de «herencia múltiple» de comportamiento.
public interface Conducible {
void acelerar(int velocidad);
void frenar();
int getVelocidad();
}
public interface ElectricamenteRecargable {
void recargar(int porcentaje);
int getNivelBateria();
}
// Una clase puede implementar múltiples interfaces
public class CocheElectrico extends Vehiculo
implements Conducible, ElectricamenteRecargable {
private int velocidad = 0;
private int bateria = 100;
public CocheElectrico(String matricula) { super(matricula); }
@Override
public void arrancar() { System.out.println("Motor eléctrico activado"); }
@Override
public void acelerar(int v) { this.velocidad += v; }
@Override
public void frenar() { this.velocidad = 0; }
@Override
public int getVelocidad() { return velocidad; }
@Override
public void recargar(int porcentaje) { bateria = Math.min(100, bateria + porcentaje); }
@Override
public int getNivelBateria() { return bateria; }
}
| Característica | Clase abstracta | Interface |
|---|---|---|
| ¿Se puede instanciar? | ❌ No | ❌ No |
| ¿Puede tener atributos? | ✅ Sí | Solo constantes (static final) |
| ¿Puede tener métodos concretos? | ✅ Sí | ✅ Sí (desde Java 8, con default) |
| ¿Herencia/implementación múltiple? | ❌ Solo una superclase | ✅ Múltiples interfaces |
| ¿Constructores? | ✅ Sí | ❌ No |
🔗 Relaciones entre objetos
Los objetos no existen de forma aislada: se relacionan entre sí de distintas formas. Comprender estas relaciones es fundamental para diseñar sistemas orientados a objetos bien estructurados.
| Relación | Descripción | Ejemplo | Palabra clave |
|---|---|---|---|
| Herencia (es un/a) | Una clase extiende a otra | Un Perro es un Animal | extends |
| Implementación | Una clase cumple un contrato | CocheElectrico implementa Conducible | implements |
| Composición (tiene un/a, fuerte) | Un objeto contiene a otro y controla su ciclo de vida | Un Motor pertenece a un Coche | Atributo creado internamente |
| Agregación (tiene un/a, débil) | Un objeto referencia a otro que existe independientemente | Un Equipo tiene Jugadores | Atributo recibido por parámetro |
| Asociación | Relación general entre objetos | Un Profesor enseña a Alumnos | Referencia entre clases |
| Dependencia | Un objeto usa temporalmente a otro | Un Servicio usa un Logger | Parámetro de método |
💡 Principio de diseño: «Favorece la composición sobre la herencia» es uno de los principios más importantes del libro Design Patterns (Gang of Four). La composición ofrece mayor flexibilidad porque las relaciones se pueden cambiar en tiempo de ejecución.
⚡ Ventajas de la POO frente a otros paradigmas
La POO no es el único paradigma de programación, pero ha demostrado ser especialmente eficaz para el desarrollo de sistemas complejos. Estas son sus principales ventajas:
🎯 Modelado natural: la POO permite representar entidades del mundo real de forma directa. Un sistema de gestión hospitalaria puede tener clases como Paciente, Médico, Consulta y Receta, reflejando la estructura del dominio.
🎯 Reutilización de código: la herencia y la composición permiten reutilizar código existente sin duplicarlo. Una clase base bien diseñada puede ser extendida por decenas de subclases especializadas.
🎯 Mantenibilidad: el encapsulamiento permite modificar la implementación interna de una clase sin afectar al código que la utiliza, siempre que la interfaz pública no cambie.
🎯 Escalabilidad: los sistemas orientados a objetos se escalan añadiendo nuevas clases, no modificando las existentes (principio abierto/cerrado — OCP).
🎯 Trabajo en equipo: cada equipo puede trabajar en clases diferentes de forma independiente, integrándose a través de interfaces bien definidas.
🎯 Testabilidad: las clases bien encapsuladas se pueden probar de forma unitaria, verificando su comportamiento de forma aislada con herramientas como JUnit.
📚 Ejemplo integrador: sistema de biblioteca
Veamos cómo los cuatro pilares de la POO trabajan juntos en un sistema real. Diseñaremos un sistema simplificado de gestión de una biblioteca que demuestra encapsulamiento, herencia, polimorfismo y abstracción.
// ABSTRACCIÓN: clase abstracta para todo material de biblioteca
public abstract class MaterialBiblioteca {
// ENCAPSULAMIENTO: atributos privados
private String titulo;
private String codigo;
private boolean prestado;
public MaterialBiblioteca(String titulo, String codigo) {
this.titulo = titulo;
this.codigo = codigo;
this.prestado = false;
}
// Método abstracto: cada tipo de material define su plazo
public abstract int getDiasPrestamo();
// Métodos concretos compartidos
public boolean prestar() {
if (prestado) {
System.out.println("'" + titulo + "' ya está prestado");
return false;
}
prestado = true;
System.out.println("'" + titulo + "' prestado por " + getDiasPrestamo() + " días");
return true;
}
public void devolver() {
prestado = false;
System.out.println("'" + titulo + "' devuelto correctamente");
}
// Getters
public String getTitulo() { return titulo; }
public String getCodigo() { return codigo; }
public boolean isPrestado() { return prestado; }
}
// HERENCIA: clases especializadas
public class Libro extends MaterialBiblioteca {
private String autor;
private int paginas;
public Libro(String titulo, String codigo, String autor, int paginas) {
super(titulo, codigo);
this.autor = autor;
this.paginas = paginas;
}
@Override
public int getDiasPrestamo() { return 14; } // 2 semanas
public String getAutor() { return autor; }
}
public class Revista extends MaterialBiblioteca {
private int numero;
private String mes;
public Revista(String titulo, String codigo, int numero, String mes) {
super(titulo, codigo);
this.numero = numero;
this.mes = mes;
}
@Override
public int getDiasPrestamo() { return 7; } // 1 semana
}
public class DVD extends MaterialBiblioteca {
private int duracionMinutos;
public DVD(String titulo, String codigo, int duracionMinutos) {
super(titulo, codigo);
this.duracionMinutos = duracionMinutos;
}
@Override
public int getDiasPrestamo() { return 3; } // 3 días
}
import java.util.ArrayList;
import java.util.List;
public class Biblioteca {
private List<MaterialBiblioteca> catalogo = new ArrayList<>();
public void agregarMaterial(MaterialBiblioteca material) {
catalogo.add(material);
}
// POLIMORFISMO: el método trabaja con la clase base
// pero ejecuta el comportamiento específico de cada subclase
public void prestarMaterial(String codigo) {
for (MaterialBiblioteca material : catalogo) {
if (material.getCodigo().equals(codigo)) {
material.prestar(); // Cada tipo tiene su plazo
return;
}
}
System.out.println("Material no encontrado: " + codigo);
}
public void mostrarDisponibles() {
System.out.println("=== Materiales disponibles ===");
for (MaterialBiblioteca m : catalogo) {
if (!m.isPrestado()) {
System.out.println("[" + m.getCodigo() + "] " + m.getTitulo()
+ " (préstamo: " + m.getDiasPrestamo() + " días)");
}
}
}
// Uso del sistema
public static void main(String[] args) {
Biblioteca bib = new Biblioteca();
bib.agregarMaterial(new Libro("Clean Code", "LIB-001", "Robert C. Martin", 464));
bib.agregarMaterial(new Revista("National Geographic", "REV-042", 256, "Febrero"));
bib.agregarMaterial(new DVD("Documental Java", "DVD-007", 90));
bib.mostrarDisponibles();
bib.prestarMaterial("LIB-001"); // 'Clean Code' prestado por 14 días
bib.prestarMaterial("DVD-007"); // 'Documental Java' prestado por 3 días
bib.mostrarDisponibles(); // Solo queda la revista
}
}
🐛 Errores comunes en POO
| Error | Problema | Solución |
|---|---|---|
Atributos public |
Rompe el encapsulamiento, cualquiera modifica el estado sin control | Usar private + getters/setters |
| Herencia excesiva | Jerarquías profundas difíciles de mantener | Preferir composición; máximo 3-4 niveles |
| Clases «Dios» | Una clase que hace demasiadas cosas | Principio de responsabilidad única (SRP) |
Olvidar @Override |
Error silencioso si el método no coincide exactamente | Usar siempre la anotación @Override |
Comparar objetos con == |
Compara referencias, no contenido | Usar .equals() y sobrescribir si es necesario |
| No inicializar atributos | Valores por defecto inesperados o NullPointerException |
Inicializar en el constructor |
✏️ Ejercicios prácticos
📘 Ejercicio 1 — Clase con encapsulamiento: Crea una clase Producto con atributos privados nombre (String), precio (double) y stock (int). Implementa un método vender(int cantidad) que reduzca el stock solo si hay suficiente, y un método aplicarDescuento(double porcentaje) que valide que el porcentaje esté entre 0 y 50.
Ver solución del Ejercicio 1
public class Producto {
private String nombre;
private double precio;
private int stock;
public Producto(String nombre, double precio, int stock) {
this.nombre = nombre;
this.precio = precio;
this.stock = stock;
}
public boolean vender(int cantidad) {
if (cantidad <= 0 || cantidad > stock) {
System.out.println("Venta inválida: stock insuficiente");
return false;
}
stock -= cantidad;
System.out.println("Vendidas " + cantidad + " unidades de " + nombre);
return true;
}
public void aplicarDescuento(double porcentaje) {
if (porcentaje < 0 || porcentaje > 50) {
System.out.println("Descuento inválido (0-50%)");
return;
}
precio -= precio * (porcentaje / 100);
System.out.println(nombre + " nuevo precio: " + precio + " €");
}
// Getters
public String getNombre() { return nombre; }
public double getPrecio() { return precio; }
public int getStock() { return stock; }
}
📘 Ejercicio 2 — Herencia y polimorfismo: Crea una jerarquía con una clase abstracta Empleado (nombre, salarioBase) y dos subclases: EmpleadoFijo (con bonus anual) y EmpleadoTemporal (con multiplicador por horas extra). Sobrescribe un método calcularSalario() en cada subclase. Crea un array de Empleado[] y calcula la nómina total usando polimorfismo.
Ver solución del Ejercicio 2
public abstract class Empleado {
protected String nombre;
protected double salarioBase;
public Empleado(String nombre, double salarioBase) {
this.nombre = nombre;
this.salarioBase = salarioBase;
}
public abstract double calcularSalario();
@Override
public String toString() {
return nombre + " → " + calcularSalario() + " €";
}
}
public class EmpleadoFijo extends Empleado {
private double bonusAnual;
public EmpleadoFijo(String nombre, double salarioBase, double bonusAnual) {
super(nombre, salarioBase);
this.bonusAnual = bonusAnual;
}
@Override
public double calcularSalario() {
return salarioBase + (bonusAnual / 12);
}
}
public class EmpleadoTemporal extends Empleado {
private int horasExtra;
private double precioHora;
public EmpleadoTemporal(String nombre, double salarioBase, int horasExtra, double precioHora) {
super(nombre, salarioBase);
this.horasExtra = horasExtra;
this.precioHora = precioHora;
}
@Override
public double calcularSalario() {
return salarioBase + (horasExtra * precioHora);
}
}
// Main: polimorfismo en acción
public class Nomina {
public static void main(String[] args) {
Empleado[] plantilla = {
new EmpleadoFijo("Ana García", 2500, 3000),
new EmpleadoTemporal("Luis Martín", 1800, 20, 15),
new EmpleadoFijo("María López", 3000, 6000)
};
double totalNomina = 0;
for (Empleado emp : plantilla) {
System.out.println(emp);
totalNomina += emp.calcularSalario();
}
System.out.println("Nómina total: " + totalNomina + " €");
}
}
📘 Ejercicio 3 — Interfaces y composición: Crea una interfaz Imprimible con un método imprimir(). Haz que las clases Factura y Informe la implementen. Luego crea una clase Impresora que reciba cualquier objeto Imprimible y lo imprima. Demuestra que la impresora puede trabajar con cualquier tipo imprimible sin conocer su clase concreta.
Ver solución del Ejercicio 3
public interface Imprimible {
String imprimir();
}
public class Factura implements Imprimible {
private String cliente;
private double total;
public Factura(String cliente, double total) {
this.cliente = cliente;
this.total = total;
}
@Override
public String imprimir() {
return "FACTURA → Cliente: " + cliente + " | Total: " + total + " €";
}
}
public class Informe implements Imprimible {
private String titulo;
private String contenido;
public Informe(String titulo, String contenido) {
this.titulo = titulo;
this.contenido = contenido;
}
@Override
public String imprimir() {
return "INFORME → " + titulo + ": " + contenido;
}
}
public class Impresora {
// Acepta cualquier Imprimible — polimorfismo por interfaz
public void procesarDocumento(Imprimible doc) {
System.out.println("Imprimiendo: " + doc.imprimir());
}
}
public class Main {
public static void main(String[] args) {
Impresora impresora = new Impresora();
impresora.procesarDocumento(new Factura("Ciberaula S.L.", 1250.00));
impresora.procesarDocumento(new Informe("Ventas Q1", "Crecimiento del 15%"));
}
}
❓ Preguntas frecuentes sobre Programación Orientada a Objetos (POO): guía completa en Java
Las dudas más comunes respondidas de forma clara y directa.
💬 Foro de discusión
¿Tienes dudas sobre Programación Orientada a Objetos (POO): guía completa en Java? Comparte tu pregunta con la comunidad.
Todavía no hay mensajes. ¡Sé el primero en participar!