Métodos de extensión
Parte de la sección Programación Orientada a Objetos del Journey de Dart de Coddy — lección 83 de 110.
A veces quieres añadir funcionalidad a una clase que no te pertenece, como los tipos integrados String o int de Dart. No puedes modificar su código fuente, y la herencia no ayuda con las clases finales. Aquí es donde entran en juego los métodos de extensión.
Las extensiones te permiten añadir nuevos métodos a tipos existentes sin modificarlos ni crear subclases:
extension StringExtras on String {
String reverse() {
return split('').reversed.join('');
}
bool get isPalindrome {
var cleaned = toLowerCase().replaceAll(' ', '');
return cleaned == cleaned.split('').reversed.join('');
}
}
void main() {
print('hello'.reverse()); // olleh
print('radar'.isPalindrome); // true
}La sintaxis es extension ExtensionName on Type. En el interior, defines métodos y getters al igual que en una clase normal. Una vez definidos, estos métodos aparecen en todas las instancias de ese tipo como si estuvieran integrados.
Las extensiones funcionan en cualquier tipo, incluyendo tus propias clases y tipos genéricos:
extension IntExtras on int {
int squared() => this * this;
}
extension ListExtras<T> on List<T> {
T? get secondOrNull => length >= 2 ? this[1] : null;
}
void main() {
print(5.squared()); // 25
print([1, 2, 3].secondOrNull); // 2
}Ten en cuenta que dentro de una extensión, this se refiere a la instancia sobre la cual se llama al método. Las extensiones son una forma limpia de mejorar los tipos existentes mientras mantienes tu código organizado y legible.
Desafío
Fácil¡Vamos a mejorar los tipos integrados de Dart con funcionalidades útiles utilizando métodos de extensión! Crearás extensiones que añaden métodos prácticos a los tipos String y List, y luego los usarás en tu archivo principal.
Organizarás tu código en dos archivos:
extensions.dart: Crea dos extensiones que añadan nuevas capacidades a los tipos existentes:- Una extensión llamada
StringUtilssobreStringcon:- Un método
countVowels()que devuelva el número de vocales (a, e, i, o, u - sin distinguir entre mayúsculas y minúsculas) en la cadena - Un getter
initialsque devuelva la primera letra de cada palabra (separada por espacios), unidas y en mayúsculas. Por ejemplo,"hello world"se convierte en"HW"
- Un método
- Una extensión llamada
ListUtils<T>sobreList<T>con:- Un método
safeGet(int index)que devuelva el elemento en el índice si existe, onullsi el índice está fuera de los límites - Un getter
hasMultipleque devuelvatruesi la lista tiene más de un elemento
- Un método
- Una extensión llamada
main.dart: Importa tus extensiones y demuéstralas en acción:- Crea una cadena
"Object Oriented Programming"y muestra su recuento de vocales en el formato:Vowels: [count] - Muestra las iniciales de la misma cadena en el formato:
Initials: [initials] - Crea una lista de enteros
[10, 20, 30] - Muestra el resultado de
safeGet(1)en el formato:Element at 1: [value] - Muestra el resultado de
safeGet(5)en el formato:Element at 5: [value] - Muestra si la lista tiene múltiples elementos en el formato:
Has multiple: [true/false]
- Crea una cadena
Recuerda que dentro de una extensión, this se refiere a la instancia sobre la que se llama al método. ¡Tus extensiones harán que estos tipos parezcan tener estos métodos integrados!
Resultado esperado:
Vowels: 9
Initials: OOP
Element at 1: 20
Element at 5: null
Has multiple: trueHoja de referencia
Los métodos de extensión le permiten agregar nueva funcionalidad a tipos existentes sin modificar su código fuente o utilizar la herencia.
La sintaxis para crear una extensión es extension ExtensionName on Type:
extension StringExtras on String {
String reverse() {
return split('').reversed.join('');
}
bool get isPalindrome {
var cleaned = toLowerCase().replaceAll(' ', '');
return cleaned == cleaned.split('').reversed.join('');
}
}Una vez definidos, los métodos de extensión se pueden llamar en instancias de ese tipo como si fueran integrados:
print('hello'.reverse()); // olleh
print('radar'.isPalindrome); // trueLas extensiones funcionan en cualquier tipo, incluyendo tipos integrados, sus propias clases y tipos genéricos:
extension IntExtras on int {
int squared() => this * this;
}
extension ListExtras<T> on List<T> {
T? get secondOrNull => length >= 2 ? this[1] : null;
}
print(5.squared()); // 25
print([1, 2, 3].secondOrNull); // 2Dentro de una extensión, this se refiere a la instancia sobre la cual se llama al método.
Pruébalo tú mismo
import 'extensions.dart';
void main() {
// Crea la cadena
String text = "Object Oriented Programming";
// TODO: Imprime el recuento de vocales usando countVowels()
// Formato: Vowels: [count]
// TODO: Imprime las iniciales usando el getter initials
// Formato: Initials: [initials]
// Crea la lista de enteros
List<int> numbers = [10, 20, 30];
// TODO: Imprime el resultado de safeGet(1)
// Formato: Element at 1: [value]
// TODO: Imprime el resultado de safeGet(5)
// Formato: Element at 5: [value]
// TODO: Imprime si la lista tiene múltiples elementos
// Formato: Has multiple: [true/false]
}
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 Repository