Menu
Coddy logo textTech

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ísticaClase abstractaInterfaz
Variables de instanciaSolo constantes
ConstructoresNo
Herencia múltipleNo (único extends)Sí (múltiples implements)
Modificadores de accesoCualquieraSolo 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.

challenge icon

Desafío

Fácil

Vamos 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 campo brand (String) y un campo year (int). Incluye un constructor para inicializar ambos campos, métodos getter para cada uno y un método abstracto startEngine() que devuelva un String. También añade un método concreto getInfo() 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() y closeRoof(), ambos devolviendo String.
  • Car.java: Crea una clase que extienda Vehicle e implemente Convertible. Un Car tiene un campo adicional numDoors (int). Usa super para inicializar los campos heredados. Implementa startEngine() para que devuelva: [brand] car engine started. Implementa openRoof() para que devuelva: [brand] roof opening y closeRoof() para que devuelva: [brand] roof closing.
  • Motorcycle.java: Crea una clase que extienda Vehicle pero NO implemente Convertible—¡las motocicletas no tienen techo! Una Motorcycle tiene un campo hasSidecar (boolean). Implementa startEngine() 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 de startEngine() 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 imprime openRoof() seguido de closeRoof() 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)
    }
}
quiz iconPonte a prueba

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