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 de «es-un»: un Dog es un Animal. Pero existe otro enfoque potente: la composición, que crea una relación de «tiene-un». En lugar de heredar 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(); // Delega al motor
print('Car is ready to drive');
}
}
void main() {
var car = Car();
car.start();
}La principal ventaja de la composición es la flexibilidad. Con la herencia, estás limitado a una sola clase padre. Con la composición, puedes combinar varios componentes e incluso intercambiarlos durante la 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 directriz habitual es «favorece 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 comportamientos sin un acoplamiento estrecho, o cuando un objeto necesite capacidades de varias fuentes.
Desafío
Fácil¡Vamos a crear un sistema informático usando composición! En lugar de intentar hacer que un ordenador herede de sus componentes, crearás clases de componentes independientes y las combinarás, porque un ordenador tiene una CPU y tiene memoria; no es una CPU ni memoria.
Organizarás el código en tres archivos:
components.dart: Crea dos clases de componentes que representen las partes del ordenador:- Una clase
CPUcon unString brandy undouble speedGHz. Incluye un métodoprocess()que devuelva una cadena:[brand] processing at [speedGHz]GHz - Una clase
Memorycon unint sizeGB. Incluye un métodoload()que devuelva una cadena:Loading [sizeGB]GB of data
- Una clase
computer.dart: Crea una claseComputerque use composición para combinar los componentes. Tu ordenador debe:- Tener un campo
String name - Contener una instancia de
CPUy una deMemory(composición: el ordenador TIENE estas partes) - Aceptar todos los valores necesarios mediante su constructor para crear ambos componentes internamente
- Tener un método
boot()que delegue en ambos componentes e imprima tres líneas: el nombre del ordenador seguido destarting..., después el resultado del método process de la CPU y, después, el resultado del método load de Memory - Tener un método
specs()que devuelva una cadena que describa el sistema:[name]: [brand] [speedGHz]GHz, [sizeGB]GB RAM
- Tener un campo
main.dart: Importa tus archivos y demuestra la composición en acción:- Crea un
ComputerllamadoWorkstationcon una CPUIntelque funcione a3.5GHz y16GB de memoria - Llama al método
boot() - Imprime el resultado de
specs()
- Crea un
Fíjate en que la clase Computer no extiende CPU ni Memory: los contiene y delega el trabajo en ellos. ¡Esta es la relación «tiene» que crea la composición!
Salida esperada:
Workstation starting...
Intel processing at 3.5GHz
Loading 16GB of data
Workstation: Intel 3.5GHz, 16GB RAMPrué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()
}
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 POO
Archivos externosLibrerías e importacionesIntroducción a la POOClases vs. ObjetosLa palabra clave thisMétodosVariables de instanciaConceptos básicos de constructoresResumen - Calculadora sencilla4Seguridad de nulos
Introducción a la seguridad de nulosNulables vs No nulablesLos operadores ? y !Palabra clave Late y Null SafetyOperadores Null-awareNull Safety en clasesResumen - Sistema de perfil de usuario7Clases abstractas e interfaces
Clases abstractasMétodos abstractosInterfaces en DartInterfaces implícitasImplementar vs. ExtenderInterfaces múltiplesRepaso - Calculadora de figuras10Colecciones y Genéricos
Visión general de List, Set y MapColecciones con seguridad de tiposClases genéricasMétodos genéricosRestricciones genéricasIterable e IteratorResumen - Almacenamiento genérico13Conceptos avanzados de POO
Composición vs. HerenciaMétodos de extensiónClases invocablesClases selladas (Dart 3)Records (Dart 3)Patrones y coincidencia (3.0)Enums con métodos2Constructores en Dart
Constructor por defectoConstructores nombradosListas de inicializaciónConstructores constantesConstructores de fábricaConstructores de redireccionamientoResumen - Constructor de formas5Encapsulamiento
Miembros públicos vs. privadosLa convención del prefijo _Privacidad a nivel de libreríaProfundidad en Getters y SettersOcultamiento de informaciónResumen - Registros de estudiantes8Mixins
Introducción a los MixinsCreación de MixinsUso de múltiples MixinsPalabra clave on en MixinsMixin vs HerenciaMixin vs InterfazResumen - Sistema Animal11Métodos especiales
Sobrescritura de toString()Sobrescritura de hashCode y ==Interfaz ComparableMétodo call()Sobrescritura de noSuchMethodResumen - Colección personalizada14Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy3Propiedades de clase
Miembros de instancia vs. estáticosCampos Final y ConstVariables LateMétodos y campos estáticosGetters y SettersResumen - Gestor de cuentas bancarias6Herencia
Herencia básicaLa palabra clave superSobrescritura de métodosLa anotación @overrideLa palabra clave final en clasesConstructores y herenciaResumen - Jerarquía de empleados9Polimorfismo
Fundamentos del polimorfismoPolimorfismo mediante interfacesComprobación de tipos en tiempo de ejecuciónLos operadores is y asPalabra clave covariantResumen - Procesador de pagos12POO Asíncrona
Futures y async/awaitFundamentos de StreamsStream ControllersConstructores asíncronosAsincronía en métodos de claseRepaso - Data Fetcher15Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón RepositoryPractica por tu cuenta: Compilador de Dart online