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é ocurre si una clase necesita funcionalidad de algo que no es? Aquí es donde entra en juego la composición, que establece una relación «tiene-un» en su lugar.
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 durante la ejecución, como intercambiar engines.
También puedes combinar comportamientos de varias clases, algo que la herencia no permite en Java. La clase compuesta solo expone los métodos que elige, lo que proporciona un mejor encapsulamiento.
Una pauta práctica: usa la herencia cuando exista una relación genuina de «es un» y la subclase represente realmente una versión especializada de la clase principal.
Usa la composición cuando necesites utilizar la funcionalidad de otra clase sin ser de ese tipo. Muchos desarrolladores con experiencia siguen el principio: «prefiere la composición a la herencia», porque da lugar a un código más flexible y fácil de mantener.
Desafío
FácilConstruyamos un sistema informático usando composición para demostrar la relación «tiene un». 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) yspeedGHz(double). Incluye un constructor para inicializar ambos campos y métodos getter para cada uno. Añade un métodoprocess()que devuelva un String:[brand] processing at [speedGHz] GHzMemory.java: Crea una clase que represente la RAM. Un Memory tiene dos campos privados:type(String, como "DDR4" o "DDR5") ysizeGB(int). Incluye un constructor, getters y un métodoload()que devuelva:Loading [sizeGB]GB [type] memoryComputer.java: ¡Aquí es donde destaca la composición! Tu clase Computer debe tener un Processor y un Memory como campos privados. No los extiende, los contiene. Incluye un constructor que acepte ambos componentes. Añade un métodoboot()que devuelva un String de varias líneas mostrando cómo la computadora se inicia utilizando sus componentes:Añade también un métodoBooting computer... [processor.process() result] [memory.load() result] System ready!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 un Memory con estos valores y, después, compónlos en un Computer. Imprime el resultado de llamar a
boot()y, después, imprime el resultado degetSpecs().
Recibirás cuatro entradas en este 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 en sus componentes en lugar de intentar serlos. ¡Este es el poder de la composición! El Computer puede utilizar cualquier Processor o Memory que le proporciones, lo que hace que el diseño sea flexible y realista.
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: Crea un Processor con la marca y la velocidad
// TODO: Crea una Memory con el tipo y el tamaño
// TODO: Crea un Computer componiendo el Processor y la Memory
// TODO: Imprime el resultado de boot()
// TODO: Imprime el resultado de getSpecs()
}
}
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 IteratorPractica por tu cuenta: Compilador de Java online