Menu
Coddy logo textTech

Composición vs. Herencia

Parte de la sección Programación Orientada a Objetos del Journey de Dart de Coddy — lección 82 de 110.

Has aprendido que la herencia crea una relación "es-un" - un Dog es un Animal. Pero existe otro enfoque poderoso: la composición, que crea una relación "tiene-un". En lugar de heredar el comportamiento, una clase contiene instancias de otras clases y les delega el trabajo.

Considera una clase Car. Con la herencia, podrías intentar extender una clase Engine, pero un coche no es un motor, tiene un motor:

class Engine {
  void start() => print('Engine started');
}

class Car {
  final Engine _engine = Engine();  // Composición: Car TIENE un Engine
  
  void start() {
    _engine.start();  // Delegar al engine
    print('Car is ready to drive');
  }
}

void main() {
  var car = Car();
  car.start();
}

La ventaja clave de la composición es la flexibilidad. Con la herencia, estás limitado a una única clase padre. Con la composición, puedes combinar múltiples componentes e incluso intercambiarlos en tiempo de ejecución:

class ElectricEngine {
  void start() => print('Electric motor humming');
}

class HybridCar {
  Engine? _gasEngine;
  ElectricEngine? _electricEngine;
  
  HybridCar({bool useElectric = false}) {
    if (useElectric) {
      _electricEngine = ElectricEngine();
    } else {
      _gasEngine = Engine();
    }
  }
}

Una pauta común es "favorecer la composición sobre la herencia". Usa la herencia cuando exista una verdadera relación "es-un" y necesites polimorfismo. Usa la composición cuando quieras reutilizar el comportamiento sin un acoplamiento fuerte, o cuando un objeto necesite capacidades de múltiples fuentes.

challenge icon

Desafío

Fácil

¡Vamos a construir un sistema informático utilizando la composición! En lugar de intentar que una computadora herede de sus partes, crearás clases de componentes separadas y las compondrás juntas, porque una computadora tiene una CPU y tiene memoria, no es una CPU ni una memoria.

Organizarás tu código en tres archivos:

  • components.dart: Crea dos clases de componentes que representen partes de la computadora:
    • Una clase CPU con un String brand y un double speedGHz. Incluye un método process() que devuelva un string: '$brand processing at ${speedGHz}GHz'
    • Una clase Memory con un int sizeGB. Incluye un método load() que devuelva un string: 'Loading ${sizeGB}GB of data'
  • computer.dart: Crea una clase Computer que utilice la composición para combinar los componentes. Tu computadora debe:
    • Tener un campo String name
    • Contener una instancia de CPU y una de Memory (composición: la computadora TIENE estas partes)
    • Aceptar todos los valores necesarios a través de su constructor para crear ambos componentes internamente
    • Tener un método boot() que delegue en ambos componentes e imprima tres líneas: el nombre de la computadora seguido de ' starting...', luego el resultado del método process de la CPU, y después el resultado del método load de la Memory
    • Tener un método specs() que devuelva un string describiendo el sistema: '$name: ${cpu.brand} ${cpu.speedGHz}GHz, ${memory.sizeGB}GB RAM'
  • main.dart: Importa tus archivos y demuestra la composición en acción:
    • Crea un Computer llamado 'Workstation' con una CPU 'Intel' funcionando a 3.5 GHz y 16 GB de memoria
    • Llama al método boot()
    • Imprime el resultado de specs()

Observa cómo la clase Computer no extiende de CPU ni de Memory; los contiene y les delega el trabajo. ¡Esta es la relación "tiene-un" (has-a) que crea la composición!

Salida esperada:

Workstation starting...
Intel processing at 3.5GHz
Loading 16GB of data
Workstation: Intel 3.5GHz, 16GB RAM

Hoja de referencia

La composición crea una relación de tipo "tiene un" (has-a) donde una clase contiene instancias de otras clases y les delega el trabajo, en lugar de heredar de ellas.

Ejemplo básico de composición:

class Engine {
  void start() => print('Engine started');
}

class Car {
  final Engine _engine = Engine();  // Car HAS an Engine
  
  void start() {
    _engine.start();  // Delegate to the engine
    print('Car is ready to drive');
  }
}

Composición con flexibilidad - intercambio de componentes:

class ElectricEngine {
  void start() => print('Electric motor humming');
}

class HybridCar {
  Engine? _gasEngine;
  ElectricEngine? _electricEngine;
  
  HybridCar({bool useElectric = false}) {
    if (useElectric) {
      _electricEngine = ElectricEngine();
    } else {
      _gasEngine = Engine();
    }
  }
}

Cuándo usar composición frente a herencia:

  • Usa la herencia para relaciones verdaderas de tipo "es un" (is-a) cuando necesites polimorfismo
  • Usa la composición para reutilizar el comportamiento sin un acoplamiento fuerte, o cuando un objeto necesite capacidades de múltiples fuentes
  • Guía general: "prioriza la composición sobre la herencia"

Pruébalo tú mismo

import 'computer.dart';

void main() {
  // TODO: Crea un Computer llamado "Workstation" con:
  // - CPU Intel funcionando a 3.5 GHz
  // - 16 GB de memoria

  // TODO: Llama al método boot()

  // TODO: Imprime el resultado de specs()
}
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