Menu
Coddy logo textTech

Composición vs. Herencia

Parte de la sección Programación Orientada a Objetos del Journey de Java de Coddy — lección 47 de 87.

La herencia crea una relación "es-un": un Dog es un Animal. Pero, ¿qué pasa si una clase necesita funcionalidad de algo que no es? Aquí es donde entra la composición, estableciendo en su lugar una relación "tiene-un".

Con la composición, una clase contiene instancias de otras clases como campos en lugar de extenderlas:

// Enfoque de herencia: Car ES UN Engine (no tiene sentido)
class Car extends Engine { }

// Enfoque de composición: Car TIENE UN Engine (tiene sentido)
class Car {
    private Engine engine;
    
    public Car(Engine engine) {
        this.engine = engine;
    }
    
    public void start() {
        engine.ignite();
    }
}

La composición ofrece varias ventajas. Puedes cambiar el objeto compuesto en tiempo de ejecución, como intercambiar motores.

También puedes combinar comportamientos de múltiples clases, algo que la herencia no permite en Java. La clase compuesta solo expone los métodos que elige, proporcionando una mejor encapsulación.

Una guía práctica: utiliza la herencia cuando exista una relación genuina de tipo "es-un" y la subclase represente verdaderamente una versión especializada del padre.

Usa la composición cuando necesites utilizar la funcionalidad de otra clase sin ser de ese tipo. Muchos desarrolladores experimentados siguen el principio: "favorecer la composición sobre la herencia" porque conduce a un código más flexible y fácil de mantener.

challenge icon

Desafío

Fácil

Vamos a construir un sistema informático utilizando la composición para demostrar la relación "tiene-un" (has-a). En lugar de que un Computer herede de sus componentes (lo cual no tendría sentido), tu computadora contendrá las partes que necesita para funcionar.

Organizarás tu código en tres archivos:

  • Processor.java: Crea una clase que represente una CPU. Un Processor tiene dos campos privados: brand (String) y speedGHz (double). Incluye un constructor para inicializar ambos campos y métodos getter para cada uno. Añade un método process() que devuelva un String: [brand] processing at [speedGHz] GHz
  • Memory.java: Crea una clase que represente la RAM. Una Memory tiene dos campos privados: type (String, como "DDR4" o "DDR5") y sizeGB (int). Incluye un constructor, getters y un método load() que devuelva: Loading [sizeGB]GB [type] memory
  • Computer.java: ¡Aquí es donde brilla la composición! Tu clase Computer debe tener un Processor y una Memory como campos privados; no los extiende, los contiene. Incluye un constructor que acepte ambos componentes. Añade un método boot() que devuelva un String multilínea que muestre la computadora iniciándose mediante el uso de sus componentes:
    Booting computer...
    [processor.process() result]
    [memory.load() result]
    System ready!
    También añade un método getSpecs() que devuelva: Specs: [processor brand] CPU, [memory sizeGB]GB [memory type]
  • Main.java: ¡Da vida a tu sistema compuesto! Recibirás cuatro entradas: marca del procesador (String), velocidad del procesador (double), tipo de memoria (String) y tamaño de la memoria (int).

    Crea un Processor y una Memory con estos valores, luego compónlos en un Computer. Imprime el resultado de llamar a boot(), luego imprime el resultado de getSpecs().

Recibirás cuatro entradas en orden: marca del procesador, velocidad del procesador (GHz), tipo de memoria y tamaño de la memoria (GB).

Observa cómo el Computer delega el trabajo a sus componentes en lugar de intentar ser ellos; ¡este es el poder de la composición! El Computer puede usar cualquier Processor o Memory que le proporciones, lo que hace que el diseño sea flexible y realista.

Hoja de referencia

La composición crea una relación "tiene un" (has-a) donde una clase contiene instancias de otras clases como campos en lugar de extenderlas.

Herencia vs Composición:

// Herencia: Car ES UN Engine (no tiene sentido)
class Car extends Engine { }

// Composición: Car TIENE UN Engine (tiene sentido)
class Car {
    private Engine engine;
    
    public Car(Engine engine) {
        this.engine = engine;
    }
    
    public void start() {
        engine.ignite();
    }
}

Ventajas de la Composición:

  • Los objetos se pueden cambiar en tiempo de ejecución
  • Puede combinar comportamientos de múltiples clases
  • Mejor encapsulación: solo expone los métodos elegidos
  • Código más flexible y fácil de mantener

Cuándo usar:

  • Herencia: Úsela cuando haya una relación genuina "es un" (is-a) y la subclase sea una versión especializada del padre
  • Composición: Úsela cuando necesite la funcionalidad de otra clase sin ser de ese tipo

Mejor práctica: "Prefiera la composición sobre la herencia"

Pruébalo tú mismo

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Leer entradas
        String processorBrand = scanner.nextLine();
        double processorSpeed = Double.parseDouble(scanner.nextLine());
        String memoryType = scanner.nextLine();
        int memorySize = Integer.parseInt(scanner.nextLine());
        
        // TODO: Crear un Processor con la marca y la velocidad
        
        // TODO: Crear una Memory con el tipo y el tamaño
        
        // TODO: Crear un Computer componiendo el Processor y la Memory
        
        // TODO: Imprimir el resultado de boot()
        
        // TODO: Imprimir el resultado de getSpecs()
    }
}
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