Fundamentos del polimorfismo
Parte de la sección Programación Orientada a Objetos del Journey de Dart de Coddy — lección 57 de 110.
El polimorfismo significa "muchas formas": es la capacidad de tratar objetos de diferentes clases a través de una interfaz común. Cuando tienes una referencia de clase padre, esta puede apuntar a cualquier objeto de clase hija, y se llama al método correcto basándose en el tipo de objeto real.
Esto es potente porque puedes escribir código que funcione con el tipo padre y, aun así, maneje automáticamente todos los tipos hijos correctamente:
class Animal {
void speak() => print('Some sound');
}
class Dog extends Animal {
@override
void speak() => print('Woof!');
}
class Cat extends Animal {
@override
void speak() => print('Meow!');
}
void main() {
Animal myPet = Dog(); // Tipo padre, objeto hijo
myPet.speak(); // Salida: Woof!
myPet = Cat(); // Misma variable, objeto diferente
myPet.speak(); // Salida: Meow!
}La variable myPet está declarada como Animal, pero contiene un Dog o Cat. Cuando se llama a speak(), Dart observa el tipo de objeto real en tiempo de ejecución y llama al método sobrescrito correspondiente.
Esto te permite escribir funciones flexibles que funcionan con cualquier subclase:
void makeAllSpeak(List<Animal> animals) {
for (var animal in animals) {
animal.speak(); // Cada animal habla a su manera
}
}
void main() {
var pets = [Dog(), Cat(), Dog()];
makeAllSpeak(pets);
// Salida: Woof! Meow! Woof!
}La función no necesita saber sobre Dog o Cat; simplemente funciona con Animal. Esto hace que tu código sea más extensible, ya que agregar nuevos tipos de animales no requiere cambios en las funciones existentes.
Desafío
FácilVamos a construir un sistema de instrumentos musicales que demuestre el polimorfismo en acción. Crearás una clase base Instrument y varios tipos de instrumentos, luego escribirás una función que pueda hacer que cualquier colección de instrumentos toque junta, sin conocer sus tipos específicos.
Organizarás tu código en dos archivos:
instruments.dart: Define tu jerarquía de instrumentos aquí:- Una clase
Instrumentcon una propiedadString namey un constructor. Incluye un métodoplay()que imprima'$name makes a sound' - Una clase
Guitarque extienda deInstrumenty sobrescribaplay()para imprimir'$name strums melodically' - Una clase
Drumque extienda deInstrumenty sobrescribaplay()para imprimir'$name beats rhythmically' - Una clase
Pianoque extienda deInstrumenty sobrescribaplay()para imprimir'$name plays harmoniously'
performConcert(List<Instrument> instruments)que imprima'Concert begins!', luego itere a través de la lista llamando aplay()en cada instrumento, y finalmente imprima'Concert ends!'- Una clase
main.dart: Importa tu archivo de instrumentos y demuestra el polimorfismo creando una banda mixta:- Crea una lista de tipo
List<Instrument>que contenga unaGuitarllamada'Acoustic', unDrumllamado'Snare', y unPianollamado'Grand' - Pasa esta lista a
performConcert()
- Crea una lista de tipo
Observa cómo performConcert() trabaja con el tipo padre Instrument, pero cada instrumento toca a su manera única. Esto es el polimorfismo: la misma llamada al método produce un comportamiento diferente basado en el tipo de objeto real.
Salida esperada:
Concert begins!
Acoustic strums melodically
Snare beats rhythmically
Grand plays harmoniously
Concert ends!Hoja de referencia
El polimorfismo significa "muchas formas"; permite tratar objetos de diferentes clases a través de una interfaz común. Una referencia de clase padre puede apuntar a cualquier objeto de clase hija, y se llama al método correcto basándose en el tipo de objeto real en tiempo de ejecución.
Ejemplo básico de polimorfismo:
class Animal {
void speak() => print('Some sound');
}
class Dog extends Animal {
@override
void speak() => print('Woof!');
}
class Cat extends Animal {
@override
void speak() => print('Meow!');
}
void main() {
Animal myPet = Dog(); // Tipo padre, objeto hijo
myPet.speak(); // Salida: Woof!
myPet = Cat(); // Misma variable, objeto diferente
myPet.speak(); // Salida: Meow!
}El polimorfismo permite funciones flexibles que funcionan con cualquier subclase:
void makeAllSpeak(List<Animal> animals) {
for (var animal in animals) {
animal.speak(); // Cada animal habla a su manera
}
}
void main() {
var pets = [Dog(), Cat(), Dog()];
makeAllSpeak(pets);
// Salida: Woof! Meow! Woof!
}La función trabaja con el tipo padre Animal sin necesidad de conocer las clases hijas específicas como Dog o Cat. Esto hace que el código sea más extensible: añadir nuevos tipos no requiere cambios en las funciones existentes.
Pruébalo tú mismo
import 'instruments.dart';
void main() {
// TODO: Crear una List<Instrument> que contenga:
// - Una Guitar llamada 'Acoustic'
// - Un Drum llamado 'Snare'
// - Un Piano llamado 'Grand'
// TODO: Llamar a performConcert() con tu lista de instrumentos
}
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érico2Constructores 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 Repository