Constructores asíncronos
Parte de la sección Programación Orientada a Objetos del Journey de Dart de Coddy — lección 79 de 110.
Los constructores en Dart no pueden marcarse como async; deben devolver una instancia de la clase inmediatamente, no un Future. ¿Entonces, cómo se crean objetos que necesitan una inicialización asíncrona, como cargar datos de una base de datos o recuperar una configuración?
La solución es utilizar un método de fábrica estático asíncrono. Mantén el constructor privado (o simple) y proporciona un método estático que realice el trabajo asíncrono y devuelva un Future de tu clase:
class UserProfile {
final String name;
final int age;
UserProfile._(this.name, this.age); // Constructor privado
static Future<UserProfile> create(String userId) async {
// Simular la obtención de datos
await Future.delayed(Duration(seconds: 1));
return UserProfile._('Alice', 25);
}
}
Future<void> main() async {
var user = await UserProfile.create('123');
print(user.name); // Alice
}El constructor privado UserProfile._ maneja la creación real del objeto, mientras que el método estático create() maneja todo el trabajo asíncrono. Los llamadores usan await para obtener el objeto completamente inicializado.
Este patrón asegura que su objeto esté completamente listo para usar una vez que lo tenga: sin estados parcialmente inicializados ni datos faltantes. Se utiliza comúnmente para clases que necesitan cargar recursos, conectarse a servicios o realizar cualquier configuración que lleve tiempo.
Desafío
Fácil¡Vamos a construir un cargador de configuración que demuestre cómo manejar la inicialización asíncrona en las clases! Dado que los constructores no pueden ser asíncronos, utilizarás el patrón de método de fábrica estático para crear objetos que necesitan tiempo para "cargar" sus datos.
Organizarás tu código en dos archivos:
config_loader.dart: Crea una claseConfigLoaderque simule la carga de ajustes de configuración de forma asíncrona. Tu clase debe tener:- Tres campos finales:
appName(String),version(String) ymaxUsers(int) - Un constructor privado que inicialice los tres campos
- Un método de fábrica estático y asíncrono llamado
loadque tome un parámetroString configIdy devuelva unFuture<ConfigLoader>. Este método debe:- Usar
Future.delayed()con 100 milisegundos para simular el tiempo de carga - Generar los valores de configuración basados en el
configId:appName: el configId con" App"concatenadoversion:"1.0."seguido de la longitud de configIdmaxUsers: la longitud de configId multiplicada por 10
- Devolver una nueva instancia de
ConfigLoadercon estos valores
- Usar
- Un método
displayInfo()que imprima la configuración en este formato:Config: [appName] v[version] (max [maxUsers] users)
- Tres campos finales:
main.dart: Importa tu cargador de configuración y demuestra el patrón de inicialización asíncrona:- Imprime
Loading configurations... - Carga una configuración con configId
"Production"y llama a sudisplayInfo() - Carga una configuración con configId
"Dev"y llama a sudisplayInfo() - Imprime
All configs loaded!
- Imprime
Observa cómo el método estático load maneja todo el trabajo asíncrono, mientras que el constructor privado simplemente asigna los valores. ¡Los llamadores usan await para obtener un objeto completamente inicializado!
Salida esperada:
Loading configurations...
Config: Production App v1.0.10 (max 100 users)
Config: Dev App v1.0.3 (max 30 users)
All configs loaded!Hoja de referencia
Los constructores en Dart no pueden marcarse como async porque deben devolver una instancia de la clase inmediatamente, no un Future.
Para crear objetos que necesiten una inicialización asíncrona, utiliza un método de fábrica estático asíncrono:
class UserProfile {
final String name;
final int age;
UserProfile._(this.name, this.age); // Constructor privado
static Future<UserProfile> create(String userId) async {
// Realiza el trabajo asíncrono aquí
await Future.delayed(Duration(seconds: 1));
return UserProfile._('Alice', 25);
}
}Uso:
Future<void> main() async {
var user = await UserProfile.create('123');
print(user.name); // Alice
}El constructor privado (marcado con _) se encarga de la creación del objeto, mientras que el método estático se encarga del trabajo asíncrono. Esto garantiza que los objetos estén completamente inicializados antes de su uso.
Pruébalo tú mismo
import 'config_loader.dart';
void main() async {
// TODO: Imprimir "Loading configurations..."
// TODO: Cargar una configuración con configId "Production"
// y llamar a su método displayInfo()
// TODO: Cargar una configuración con configId "Dev"
// y llamar a su método displayInfo()
// TODO: Imprimir "All configs loaded!"
}
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