Fundamentos de OOP: Clase, Objeto, Instancia y Métodos para el Examen IHK
La Programación Orientada a Objetos (OOP) es un paradigma fundamental en el desarrollo moderno de software. Este documento de aprendizaje te prepara específicamente para el examen IHK y explica los conceptos clave de forma clara con ejemplos prácticos.
¿Por qué OOP? La introducción
Imagina que construyes un automóvil. No reinventarías cada tornillo, cada rueda y cada pieza cada vez. Utilizas partes prefabricadas y reutilizables (como neumáticos, asientos, motores) que cumplen funciones específicas y trabajan en conjunto. Así funciona también la OOP. Es un paradigma de programación que organiza el software a partir de “bloques de construcción” reutilizables y bien estructurados: los objetos. Esto hace que el software sea más mantenible, extensible y comprensible.
La diferencia central: Clase vs Objeto (Instancia)
Este es el fundamento. Si lo entiendes, todo lo demás tendrá sentido.
Clase (Plano)
Una clase es un plano abstracto o una plantilla. Define qué atributos (propiedades) y métodos (capacidades) tendrán los objetos que se creen a partir de ella.
Ejemplo: La clase Auto. El plano dice: “Un auto tiene los atributos color, numerosPuertas y potenciaEnCV. Puede ejecutar los métodos frenar() y acelerar()”.
La clase existe una sola vez en el código.
Objeto / Instancia (algo concreto)
Un objeto es una manifestación concreta (una “instancia”) de una clase. Se crea durante la ejecución del programa a partir de la clase (con la palabra clave new en lenguajes como Java o C#).
Ejemplo: El objeto miGolf es una instancia de la clase Auto. Tiene los valores de atributos concretos color = "azul", numerosPuertas = 5, potenciaEnCV = 150. El objeto tuA3 es otra instancia con los valores color = "negro", numerosPuertas = 3, potenciaEnCV = 120.
De una clase pueden crearse tantos objetos como sea necesario.
Analogía simple
- Clase = El plano de una casa (define que hay paredes, puertas, ventanas)
- Objeto = La casa realmente construida (dirección concreta, pared de color blanco, puerta de madera de roble)
// Ejemplo Java: Clase vs Objeto
public class Auto {
// Atributos (propiedades)
private String color;
private int numerosPuertas;
private int potenciaEnCV;
// Constructor
public Auto(String color, int numerosPuertas, int potenciaEnCV) {
this.color = color;
this.numerosPuertas = numerosPuertas;
this.potenciaEnCV = potenciaEnCV;
}
// Métodos (capacidades)
public void acelerar() {
System.out.println("El auto " + color + " acelera");
}
public void frenar() {
System.out.println("El auto " + color + " frena");
}
// Getters y Setters
public String getColor() { return color; }
public void setColor(String color) { this.color = color; }
public int getNumerosPuertas() { return numerosPuertas; }
public int getPotenciaEnCV() { return potenciaEnCV; }
}
// Crear objetos (instanciación)
public class Main {
public static void main(String[] args) {
// Objeto 1
Auto miGolf = new Auto("azul", 5, 150);
miGolf.acelerar();
// Objeto 2
Auto tuA3 = new Auto("negro", 3, 120);
tuA3.frenar();
System.out.println("Mi Golf: " + miGolf.getColor() + ", " +
miGolf.getNumerosPuertas() + " puertas, " +
miGolf.getPotenciaEnCV() + " CV");
}
}
Los cuatro principios (pilares) de la OOP
1. Encapsulación (Encapsulation)
¿Qué es? La agrupación de datos (atributos) y el código que manipula esos datos (métodos) en una unidad: la clase. El acceso a los datos se controla “ocultándolos” hacia el exterior.
¿Cómo funciona? Los atributos se declaran típicamente como private. Esto significa que desde fuera de la clase nadie puede acceder directamente a miGolf.potenciaEnCV ni modificarlo. El acceso ocurre exclusivamente a través de métodos públicos llamados getters y setters (también llamados métodos de acceso).
getPotenciaEnCV(): Devuelve el valor (lectura)setPotenciaEnCV(int nuevaPotencia): Modifica el valor (escritura)
¿Por qué es tan importante?
- Control: La clase puede verificar en el método
setque el nuevo valor tiene sentido (por ejemplo,nuevaPotenciano puede ser negativa) - Mantenibilidad: Si la representación interna cambia (por ejemplo, guardas de repente la potencia en kW en lugar de CV), solo necesitas ajustar los métodos getter/setter, no todo el código que usa estos valores
- Consistencia de datos: Evita que los objetos se encuentren en un estado inválido
// Ejemplo de encapsulación
public class CuentaBancaria {
// Atributos privados - no accesibles desde fuera
private double saldo;
private String numeroCuenta;
private String titular;
public CuentaBancaria(String numeroCuenta, String titular, double montoInicial) {
this.numeroCuenta = numeroCuenta;
this.titular = titular;
this.saldo = Math.max(0, montoInicial); // El saldo no puede ser negativo
}
// Getter - acceso de lectura controlado
public double getSaldo() {
return saldo;
}
// Setter - acceso de escritura controlado con validación
public void setSaldo(double nuevoSaldo) {
if (nuevoSaldo >= 0) {
this.saldo = nuevoSaldo;
} else {
System.out.println("Error: El saldo no puede ser negativo");
}
}
// Método de negocio con encapsulación
public void extraer(double monto) {
if (monto > 0 && saldo >= monto) {
saldo -= monto;
System.out.println("Extracción exitosa. Nuevo saldo: " + saldo);
} else {
System.out.println("Extracción no posible. Monto inválido o fondos insuficientes.");
}
}
public void depositar(double monto) {
if (monto > 0) {
saldo += monto;
System.out.println("Depósito exitoso. Nuevo saldo: " + saldo);
} else {
System.out.println("Depósito no posible. El monto debe ser positivo.");
}
}
}
2. Herencia (Inheritance)
¿Qué es? La posibilidad de definir una nueva clase (clase hija o subclase) basándose en una clase ya existente (clase padre u clase base). La clase hija hereda todos los atributos y métodos de la clase padre.
¿Por qué se hace? Para evitar repetir código (principio DRY: Don’t Repeat Yourself) y representar relaciones jerárquicas.
Ejemplo:
- Clase base:
Vehiculo(con atributosvelocidad,fabricantey métodomover()) - Subclase:
Auto(hereda deVehiculoy añade un atributo propionumerosPuertas) - Subclase:
Bicicleta(hereda deVehiculoy añade un atributo propionumeroCambios)
Un objeto Auto tiene automáticamente también velocidad y puede ejecutar mover().
// Ejemplo de herencia
// Clase base (clase padre)
public class Vehiculo {
protected String fabricante;
protected int velocidad;
protected int anio;
public Vehiculo(String fabricante, int anio) {
this.fabricante = fabricante;
this.anio = anio;
this.velocidad = 0;
}
public void acelerar(int incremento) {
this.velocidad += incremento;
System.out.println(fabricante + " acelera a " + velocidad + " km/h");
}
public void frenar(int decremento) {
this.velocidad = Math.max(0, velocidad - decremento);
System.out.println(fabricante + " frena a " + velocidad + " km/h");
}
public void mover() {
System.out.println("El vehículo " + fabricante + " se mueve a " + velocidad + " km/h");
}
// Getters
public String getFabricante() { return fabricante; }
public int getVelocidad() { return velocidad; }
public int getAnio() { return anio; }
}
// Subclase 1
public class Auto extends Vehiculo {
private int numerosPuertas;
private String tipoCombustible;
public Auto(String fabricante, int anio, int numerosPuertas, String tipoCombustible) {
super(fabricante, anio); // Llamada al constructor de la clase padre
this.numerosPuertas = numerosPuertas;
this.tipoCombustible = tipoCombustible;
}
// Método propio de la subclase
public void tocar() {
System.out.println("El auto " + fabricante + " toca la bocina");
}
// Sobreescribir método de la clase padre
@Override
public void mover() {
System.out.println("El auto " + numerosPuertas + " puertas " + fabricante +
" circula en la calle a " + velocidad + " km/h");
}
// Getters
public int getNumerosPuertas() { return numerosPuertas; }
public String getTipoCombustible() { return tipoCombustible; }
}
// Subclase 2
public class Bicicleta extends Vehiculo {
private int numeroCambios;
private String tipoVehiculo = "Bicicleta";
public Bicicleta(String fabricante, int anio, int numeroCambios) {
super(fabricante, anio);
this.numeroCambios = numeroCambios;
}
// Método propio
public void cambiar(int cambio) {
if (cambio >= 1 && cambio <= numeroCambios) {
System.out.println("Cambio a velocidad " + cambio);
} else {
System.out.println("Cambio inválido");
}
}
// Sobreescribir método
@Override
public void mover() {
System.out.println("La bicicleta " + fabricante + " con " + numeroCambios +
" cambios se mueve a " + velocidad + " km/h");
}
}
3. Polimorfismo
¿Qué es? La capacidad de que un objeto de una subclase pueda tratarse también como un objeto de su clase padre. Los métodos pueden ser sobrescritos en las subclases para implementar comportamientos específicos.
Ejemplo: El método move() se define en la clase padre Vehículo. En la clase Auto se sobrescribe para imprimir “El auto circula por la carretera”. En la clase Avión (que también hereda de Vehículo) se sobrescribe para imprimir “El avión vuela por el aire”.
Ventaja: El código que invoca el método move() no necesita saber de qué tipo concreto (Auto o Avión) se trata. Simplemente llama a move() sobre el objeto de tipo Vehículo. En tiempo de ejecución, se invoca automáticamente el método sobrescrito correcto de la clase real del objeto. Esto hace que el código sea sumamente flexible.
// Ejemplo de polimorfismo
public class PolimorfismoDemo {
public static void main(String[] args) {
// Diferentes objetos vehículo
Vehículo miAuto = new Auto("VW", 2020, 4, "Gasolina");
Vehículo miBicicleta = new Bicicleta("Cube", 2021, 21);
Vehículo miMotocicleta = new Motocicleta("Yamaha", 2019, 600);
// Polimorfismo: todos se tratan como Vehículo
Vehículo[] vehiculos = {miAuto, miBicicleta, miMotocicleta};
// Enlace dinámico: en tiempo de ejecución se invoca el método correcto
for (Vehículo vehiculo : vehiculos) {
vehiculo.acelera(50);
vehiculo.move(); // Invoca el método sobrescrito de cada subclase
vehiculo.frena(20);
System.out.println("---");
}
// Verificación de tipo y conversión descendente
for (Vehículo vehiculo : vehiculos) {
if (vehiculo instanceof Auto) {
Auto auto = (Auto) vehiculo; // Conversión descendente
auto.toca();
} else if (vehiculo instanceof Bicicleta) {
Bicicleta bicicleta = (Bicicleta) vehiculo;
bicicleta.cambia(5);
}
}
}
}
// Subclase adicional para el ejemplo
class Motocicleta extends Vehículo {
private int cilindrada;
public Motocicleta(String fabricante, int anio, int cilindrada) {
super(fabricante, anio);
this.cilindrada = cilindrada;
}
@Override
public void move() {
System.out.println("La motocicleta " + fabricante + " con " + cilindrada +
" cc circula a " + velocidad + " km/h");
}
public void caballito() {
System.out.println("¡La motocicleta " + fabricante + " hace un caballito!");
}
}
4. Abstracción
¿Qué es? El principio de reducir la complejidad del mundo real a solo las propiedades esenciales para el contexto, ocultando los detalles innecesarios.
Ejemplo: Cuando modelamos un auto como un objeto Auto, nos importan atributos como potencia y métodos como frena(). Los detalles sobre cómo funciona exactamente el freno (hidráulica, pastillas de freno, etc.) son irrelevantes para la mayoría de los programas y quedan “abstraídos”. Simplemente invocas frena().
La abstracción se logra principalmente a través de dos mecanismos:
- Clases abstractas: Clases que no pueden instanciarse, sino que sirven como plantilla para otras clases. Pueden definir métodos abstractos (métodos sin cuerpo que la subclase debe implementar).
- Interfaces (Interfaz): Véase la siguiente sección.
// Ejemplo de abstracción
// Clase abstracta
public abstract class Animal {
protected String nombre;
protected int edad;
public Animal(String nombre, int edad) {
this.nombre = nombre;
this.edad = edad;
}
// Método concreto
public void duerme() {
System.out.println(nombre + " duerme.");
}
public void come() {
System.out.println(nombre + " come.");
}
// Métodos abstractos que deben implementar las subclases
public abstract void haceRuido();
public abstract void seMoviliza();
// Getters
public String getNombre() { return nombre; }
public int getEdad() { return edad; }
}
// Subclases concretas
public class Perro extends Animal {
private String raza;
public Perro(String nombre, int edad, String raza) {
super(nombre, edad);
this.raza = raza;
}
@Override
public void haceRuido() {
System.out.println(nombre + " ladra: ¡Guau! ¡Guau!");
}
@Override
public void seMoviliza() {
System.out.println(nombre + " corre en cuatro patas.");
}
public void traeeObjeto() {
System.out.println(nombre + " trae la pelota.");
}
}
public class Gato extends Animal {
private String colorPelaje;
public Gato(String nombre, int edad, String colorPelaje) {
super(nombre, edad);
this.colorPelaje = colorPelaje;
}
@Override
public void haceRuido() {
System.out.println(nombre + " maúlla: ¡Miau!");
}
@Override
public void seMoviliza() {
System.out.println(nombre + " se desliza sigilosamente.");
}
public void rasguña() {
System.out.println(nombre + " rasguña el rascador.");
}
}
Conceptos clave de la POO
Atributo (Propiedad)
Una variable que describe un estado o característica de un objeto (por ejemplo, color, edad, saldoCuenta).
Método
Una función o procedimiento definido en una clase que describe un comportamiento del objeto (por ejemplo, paga(), imprimeCargo()). Una invocación de método (o mensaje) es la solicitud a un objeto para que ejecute un método específico (por ejemplo, miCuenta.retira(50)).
Persistencia
El almacenamiento permanente del estado de objetos (sus valores de atributos) de modo que se conserve más allá de la finalización del programa. Esto ocurre típicamente en una base de datos o un archivo. Un objeto en sí “existe” solo en la memoria principal (RAM) durante la ejecución del programa.
// Ejemplo de persistencia
import java.io.*;
import java.util.*;
public class EjemploPersistencia {
// Guardar objeto
public static void guardaCuenta(CuentaBancaria cuenta, String nombreArchivo) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(nombreArchivo))) {
oos.writeObject(cuenta);
System.out.println("Cuenta guardada en " + nombreArchivo);
} catch (IOException e) {
System.out.println("Error al guardar: " + e.getMessage());
}
}
// Cargar objeto
public static CuentaBancaria cargaCuenta(String nombreArchivo) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(nombreArchivo))) {
CuentaBancaria cuenta = (CuentaBancaria) ois.readObject();
System.out.println("Cuenta cargada desde " + nombreArchivo);
return cuenta;
} catch (IOException | ClassNotFoundException e) {
System.out.println("Error al cargar: " + e.getMessage());
return null;
}
}
public static void main(String[] args) {
CuentaBancaria miCuenta = new CuentaBancaria("DE123456789", "Max Mustermann", 1000.0);
miCuenta.deposita(500.0);
// Guardar
guardaCuenta(miCuenta, "cuenta.ser");
// Cargar
CuentaBancaria cuentaCargada = cargaCuenta("cuenta.ser");
if (cuentaCargada != null) {
System.out.println("Cuenta cargada - Saldo: " + cuentaCargada.getSaldo());
}
}
}
// Interfaz Serializable para persistencia
class CuentaBancaria implements Serializable {
private static final long serialVersionUID = 1L;
private double saldo;
private String numeroCuenta;
private String titular;
// ... resto de la implementación como se mostró arriba
}
Interfaz / API
(Atención: este término tiene dos significados)
-
En general: Una interfaz (o API - Application Programming Interface) define un contrato, qué puede hacer algo, sin especificar cómo se hace. Es una colección de firmas de métodos (nombre, parámetros, tipo de retorno).
-
Como construcción del lenguaje (
interface): Un construcción especial en lenguajes OOP que define formalmente estos contratos. Una clase que implementa unainterfacedebe implementar todos los métodos definidos en ella (llenarlos con código).
La diferencia: Clase vs. Interfaz
| Aspecto | Clase | Interfaz |
|---|---|---|
| ¿Qué es? | Un plano para crear objetos | Un contrato o especificación de capacidades |
| Contiene… | Implementación (código de los métodos), atributos, constructores | Solo firmas de métodos (nombre, parámetros, tipo de retorno) sin implementación (sin cuerpo de código) |
| Palabras clave | class, extends (herencia) | interface, implements (implementar) |
| Herencia | Una clase puede heredar de una sola otra clase (herencia simple) | Una clase puede implementar múltiples interfaces |
| Instanciación | Se puede instanciar con new (crear objetos) | NO se puede instanciar |
| Propósito | Implementar estado y comportamiento | Especificar un comportamiento |
Analogía
- Clase = Un empleado específico: “Max Mustermann, desarrollador de software” conoce la empresa SAP y puede escribir código Java (implementación)
- Interfaz = Una oferta de empleo: “Buscamos un desarrollador Java con conocimientos de SAP”. Solo lista los requisitos (habilidades) que el candidato debe tener
// Ejemplo de interfaz
interface Fliegend {
void fliegen();
void landen();
}
interface Schwimmend {
void schwimmen();
void tauchen();
}
// Clase implementa múltiples interfaces
public class Ente implements Fliegend, Schwimmend {
private String name;
public Ente(String name) {
this.name = name;
}
// Implementar métodos de la interfaz
@Override
public void fliegen() {
System.out.println(name + " fliegt in die Luft");
}
@Override
public void landen() {
System.out.println(name + " landet auf dem Wasser");
}
@Override
public void schwimmen() {
System.out.println(name + " schwimmt elegant");
}
@Override
public void tauchen() {
System.out.println(name + " taucht nach Fischen");
}
// Método propio
public void quaken() {
System.out.println(name + " quakt: Quak! Quak!");
}
}
// Polimorfismo con interfaces
public class InterfaceDemo {
public static void main(String[] args) {
Ente meineEnte = new Ente("Donald");
// Tratar como Ente
meineEnte.quaken();
// Tratar como Fliegend
Fliegend fliegendeEnte = meineEnte;
fliegendeEnte.fliegen();
fliegendeEnte.landen();
// Tratar como Schwimmend
Schwimmend schwimmendeEnte = meineEnte;
schwimmendeEnte.schwimmen();
schwimmendeEnte.tauchen();
}
}
La diferencia: Biblioteca vs. Framework
Esta es una distinción conceptual importante que suele aparecer en las pruebas.
Biblioteca (Library / SDK)
- Tú controlas el flujo. Una biblioteca es una colección de clases y métodos prefabricados que puedes usar en tu código, cuándo y cómo lo necesites.
- Analogía: Un caja de herramientas. Construyes un estante y sacas de la caja un martillo (
Hammerklasse.schlagZu()), luego una sierra (Saegeklasse.saege()). Tú decides el orden y el propósito. - Ejemplos: Java Standard Library, Apache Commons, jQuery.
Framework
- El framework controla el flujo. Un framework es un esqueleto o andamio fundamental para un tipo específico de aplicación. Llenas los lugares predefinidos con tu código. El framework llama a tu código (principio del Hollywood Principle: “Don’t call us, we’ll call you”).
- Analogía: El framework es el plano de una casa completa con paredes y tuberías prefabricadas. Solo necesitas instalar los enchufes y elegir el papel tapiz (insertar tu código en los métodos predefinidos). El plano (el framework) determina el flujo.
- Ejemplos: Spring (Java), .NET Framework, Angular, React (Web), JUnit (Testing).
En resumen: Usas una biblioteca. Trabajas dentro de un framework.
// Ejemplo biblioteca vs. framework
// Biblioteca: Tú llamas los métodos
import java.util.ArrayList;
import java.util.Collections;
public class BibliothekBeispiel {
public static void main(String[] args) {
// Tú controlas el flujo
ArrayList<String> namen = new ArrayList<>();
namen.add("Alice");
namen.add("Bob");
namen.add("Charlie");
// Usas métodos de la biblioteca cuando quieres
Collections.sort(namen);
System.out.println("Sortierte Namen: " + namen);
}
}
// Framework: El framework llama tu código
// Ejemplo con JUnit Testing Framework
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class FrameworkBeispiel {
@Test // El framework llama este método
public void testAddition() {
// Llenas el método predefinido con tu código
int result = 2 + 3;
assertEquals(5, result, "2 + 3 sollte 5 ergeben");
}
@Test // Otro método que el framework llama
public void testSubtraction() {
int result = 10 - 4;
assertEquals(6, result, "10 - 4 sollte 6 ergeben");
}
}
Resumen para el examen IHK
- Clase vs. Objeto: Plano vs. cosa concreta
- Los 4 principios:
- Encapsulación: Oculta datos, ofrece acceso controlado
- Herencia: Hereda propiedades y métodos, evita redundancia
- Polimorfismo: Sobrescribe métodos para permitir comportamiento específico del subtipo
- Abstracción: Oculta detalles innecesarios
- Interfaz vs. Clase: La interfaz define qué, la clase define cómo
- Biblioteca vs. Framework: Usas la biblioteca, el framework te usa
Conceptos relevantes para el examen
Distinciones importantes
| Concepto | Descripción | Relevante |
|---|---|---|
| Clase | Plano para crear objetos | ✅ |
| Objeto/Instancia | Manifestación concreta de una clase | ✅ |
| Atributo | Propiedad de un objeto | ✅ |
| Método | Comportamiento de un objeto | ✅ |
| Herencia | Reutilización de código mediante jerarquía | ✅ |
| Polimorfismo | Versatilidad mediante sobrescritura | ✅ |
| Encapsulación | Ocultación de datos con Getter/Setter | ✅ |
| Abstracción | Reducción de complejidad | ✅ |
Tareas típicas del examen
- Explica la diferencia entre clase y objeto
- Describe los cuatro principios OOP
- Implementa una jerarquía de herencia simple
- Explica la encapsulación con métodos Getter/Setter
- Distingue interfaz de clase
- Compara biblioteca y framework
Estos fundamentos son esenciales para entender el desarrollo moderno de software y forman la base para conceptos avanzados como Design Patterns, frameworks y principios arquitectónicos.
Continúa en la ruta de aprendizaje de OOP
El siguiente artículo en la ruta de aprendizaje de OOP cubre Relaciones entre clases en OOP: Asociación, Agregación, Composición y Herencia para el examen de la IHK — relaciones entre clases para el examen de la IHK.

