Clases Abstractas vs Interfaces
Parte de la sección Programación Orientada a Objetos del Journey de Java de Coddy — lección 38 de 87.
Ahora que entiendes tanto las clases abstractas como las interfaces, surge una pregunta común: ¿cuándo deberías usar una sobre la otra? La elección depende de lo que estés intentando modelar.
Utilice una clase abstracta cuando las clases comparten una base común con estado o comportamiento compartido. Las clases abstractas pueden tener variables de instancia, constructores y una mezcla de métodos abstractos y concretos:
public abstract class Animal {
protected String name; // estado compartido
public Animal(String name) {
this.name = name;
}
public void sleep() { // comportamiento compartido
System.out.println(name + " is sleeping");
}
public abstract void makeSound(); // debe ser implementado
}Utiliza una interfaz cuando quieras definir una capacidad que clases no relacionadas puedan compartir. Las interfaces se centran en lo que un objeto puede hacer, no en lo que es:
public interface Flyable {
void fly();
}
// Clases no relacionadas pueden compartir esta capacidad
class Bird extends Animal implements Flyable { ... }
class Airplane implements Flyable { ... }
class Drone implements Flyable { ... }Aquí tienes una comparación rápida:
| Característica | Clase abstracta | Interfaz |
|---|---|---|
| Variables de instancia | Sí | Solo constantes |
| Constructores | Sí | No |
| Herencia múltiple | No (único extends) | Sí (múltiples implements) |
| Modificadores de acceso | Cualquiera | Solo públicos (para métodos abstractos) |
Una guía práctica: si te encuentras creando una clase abstracta con solo métodos abstractos y sin estado, una interfaz es probablemente la mejor opción.
Desafío
FácilVamos a construir un sistema de vehículos que demuestre cuándo usar clases abstractas frente a interfaces. Modelarás vehículos que comparten un estado y comportamiento común a través de una clase abstracta, mientras añades capacidades opcionales a través de interfaces.
Organizarás tu código en cinco archivos:
Vehicle.java: Crea una clase abstracta que sirva como base para todos los vehículos. Cada vehículo tiene un campobrand(String) y un campoyear(int). Incluye un constructor para inicializar ambos campos, métodos getter para cada uno y un método abstractostartEngine()que devuelva un String. También añade un método concretogetInfo()que devuelva:[brand] ([year]). Este es un caso de uso perfecto para una clase abstracta: los vehículos comparten estado (marca, año) y cierto comportamiento (getInfo), pero cada uno arranca su motor de manera diferente.Convertible.java: Define una interfaz para vehículos que pueden convertir su techo. Esta capacidad no está ligada a lo que un vehículo es; es algo que ciertos vehículos pueden hacer. Declara dos métodos:openRoof()ycloseRoof(), ambos devolviendo String.Car.java: Crea una clase que extiendaVehiclee implementeConvertible. Un Car tiene un campo adicionalnumDoors(int). Usasuperpara inicializar los campos heredados. ImplementastartEngine()para que devuelva:[brand] car engine started. ImplementaopenRoof()para que devuelva:[brand] roof openingycloseRoof()para que devuelva:[brand] roof closing.Motorcycle.java: Crea una clase que extiendaVehiclepero NO implementeConvertible—¡las motocicletas no tienen techo! Una Motorcycle tiene un campohasSidecar(boolean). ImplementastartEngine()para que devuelva:[brand] motorcycle engine roaring.Main.java: Reúne todo para mostrar la diferencia entre clases abstractas e interfaces. Recibirás cuatro entradas: una marca de coche, un año de coche, una marca de motocicleta y un año de motocicleta.Crea un Car (con 4 puertas) y una Motorcycle (sin sidecar). Primero, demuestra el comportamiento compartido de la clase abstracta imprimiendo
getInfo()para ambos vehículos; ambos heredan esto de Vehicle. Luego imprime el resultado destartEngine()para cada uno; observa cómo cada tipo de vehículo implementa esto de manera diferente.Finalmente, demuestra la capacidad de la interfaz: dado que solo el Car implementa
Convertible, llama e imprimeopenRoof()seguido decloseRoof()solo en el coche.
Recibirás cuatro entradas: marca del coche (String), año del coche (int), marca de la motocicleta (String) y año de la motocicleta (int).
Tu salida debe mostrar seis líneas en total. Observa cómo ambos vehículos comparten el estado de la clase abstracta y el método getInfo(), pero solo el Car tiene la capacidad de descapotable—¡esto ilustra cuándo usar cada enfoque!
Pruébalo tú mismo
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Leer entradas
String carBrand = scanner.nextLine();
int carYear = scanner.nextInt();
scanner.nextLine(); // consumir nueva línea
String motorcycleBrand = scanner.nextLine();
int motorcycleYear = scanner.nextInt();
// TODO: Crear un Car con 4 puertas
// TODO: Crear una Motorcycle sin sidecar (false)
// TODO: Imprimir getInfo() para ambos vehículos (demuestra el comportamiento compartido de la clase abstracta)
// TODO: Imprimir startEngine() para ambos vehículos (demuestra diferentes implementaciones)
// TODO: Imprimir openRoof() y closeRoof() solo para el coche (demuestra la capacidad de la interfaz)
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosIntroducción a la OOPClases vs. ObjetosLa palabra clave thisMétodosCampos (Atributos)Método constructorSobrecarga de constructoresResumen - Calculadora simple4Herencia
Herencia básica (extends)La palabra clave superSobrescritura de métodos (@Override)Encadenamiento de constructoresLa clase ObjectHerencia simple y multinivelPor qué no hay herencia múltiple de clasesResumen - Jerarquía de empleados7Métodos especiales y la clase Object
Método toString()equals() y hashCode()Método clone()compareTo() y ComparableInterfaz ComparatorResumen - Ordenamiento personalizado2Modificadores de acceso y encapsulamiento
Visión general de los niveles de accesoMétodos Getter y SetterOcultamiento de informaciónLa palabra clave finalResumen - Gestor de cuentas bancarias5Polimorfismo
Fundamentos de sobrecarga de métodosSobrescritura de métodos (tiempo de ejecución)Upcasting y DowncastingEl operador instanceofClases y métodos abstractosResumen - Calculadora de figuras8Conceptos avanzados de POO
Composición vs. HerenciaAgregación vs. ComposiciónClases internas, anidadas y anónimasEnums y métodos de EnumRecords (Java 16+)Clases selladas (Java 17+)11Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón BuilderPatrón ObserverPatrón Strategy3Propiedades de clase y miembros estáticos
Variables de instancia vs. estáticasMétodos estáticosBloques estáticosConstantes (static final)Repaso - Contador y utilidad6Interfaces y Clases Abstractas
Introducción a las InterfacesImplementación de InterfacesImplementación de Múltiples InterfacesMétodos Default y Static en InterfacesClases Abstractas vs InterfacesInterfaces FuncionalesResumen - Sistema de Pagos9Genéricos
Introducción a los genéricosClases genéricasMétodos genéricosParámetros de tipo delimitadosComodines (?, extends, super)Resumen - Contenedor genérico12Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Iterator