Clases selladas (Dart 3)
Parte de la sección Programación Orientada a Objetos del Journey de Dart de Coddy — lección 85 de 110.
Dart 3 introdujo las clases selladas, las cuales restringen qué clases pueden extenderlas o implementarlas. Cuando marcas una clase como sealed, solo las clases en el mismo archivo pueden ser sus subtipos directos. Esto crea un conjunto cerrado de subtipos conocidos.
sealed class Result {}
class Success extends Result {
final String data;
Success(this.data);
}
class Failure extends Result {
final String error;
Failure(this.error);
}El poder de las clases selladas proviene de la comprobación de exhaustividad. Cuando realizas un switch sobre un tipo sellado, el compilador conoce todos los subtipos posibles y se asegura de que manejes cada uno de ellos:
String handleResult(Result result) {
return switch (result) {
Success(data: var d) => 'Got: $d',
Failure(error: var e) => 'Error: $e',
};
// No se necesita un valor predeterminado: ¡el compilador sabe que todos los casos están cubiertos!
}
void main() {
var success = Success('Hello');
var failure = Failure('Not found');
print(handleResult(success)); // Obtenido: Hello
print(handleResult(failure)); // Error: Not found
}Si añades un nuevo subtipo a una sealed class, el compilador marcará cada sentencia switch que no lo maneje. Esto hace que las sealed classes sean ideales para modelar estados, resultados o cualquier escenario donde tengas un conjunto fijo de variantes. A diferencia de las abstract classes, las sealed classes garantizan que ningún código externo pueda añadir subtipos inesperados.
Desafío
Fácil¡Vamos a construir un manejador de peticiones de red utilizando clases selladas (sealed classes)! Crearás un sistema con seguridad de tipos que modela diferentes estados de una respuesta de red, asegurando que cada resultado posible se maneje adecuadamente.
Organizarás tu código en dos archivos:
response.dart: Crea una jerarquía de clases selladas que represente todos los estados posibles de una respuesta de red:- Una clase sellada llamada
NetworkResponseque sirva como base para todos los tipos de respuesta - Una clase
Loadingque extiendaNetworkResponsecon un campoString message(por ejemplo, "Fetching data...") - Una clase
Successque extiendaNetworkResponsecon un campoString datay un campoint statusCode - Una clase
Errorque extiendaNetworkResponsecon un campoString errorMessagey un campoint errorCode
- Una clase sellada llamada
main.dart: Importa tu archivo de respuesta y crea una función que maneje todos los tipos de respuesta utilizando una expresión switch:- Crea una función
handleResponse(NetworkResponse response)que devuelva unString - Usa una expresión switch con coincidencia de patrones (pattern matching) para manejar cada caso:
- Para
Loading: devuelveStatus: [message] - Para
Success: devuelveSuccess ([statusCode]): [data] - Para
Error: devuelveError ([errorCode]): [errorMessage]
- Para
- Crea tres instancias de respuesta e imprime el resultado de manejar cada una:
- Un
Loadingcon el mensajePlease wait... - Un
Successcon los datosUser profile loadedy el código de estado200 - Un
Errorcon el mensaje de errorNot foundy el código de error404
- Un
- Crea una función
La belleza de las clases selladas es que el compilador asegura que manejes cada tipo de respuesta posible: ¡no se necesita un caso por defecto!
Salida esperada:
Status: Please wait...
Success (200): User profile loaded
Error (404): Not foundHoja de referencia
Las clases selladas (sealed classes) restringen qué clases pueden extenderlas o implementarlas. Solo las clases en el mismo archivo pueden ser subtipos directos, creando un conjunto cerrado de variantes conocidas.
sealed class Result {}
class Success extends Result {
final String data;
Success(this.data);
}
class Failure extends Result {
final String error;
Failure(this.error);
}Las clases selladas permiten la comprobación de exhaustividad en las expresiones switch. El compilador conoce todos los subtipos posibles y garantiza que manejes cada uno de ellos:
String handleResult(Result result) {
return switch (result) {
Success(data: var d) => 'Got: $d',
Failure(error: var e) => 'Error: $e',
};
// No default case needed - all cases are covered
}Si agregas un nuevo subtipo, el compilador marcará cada sentencia switch que no lo maneje. Esto hace que las clases selladas sean ideales para modelar estados, resultados o cualquier conjunto fijo de variantes donde desees garantías en tiempo de compilación de que todos los casos están cubiertos.
Pruébalo tú mismo
import 'response.dart';
// TODO: Crea una función handleResponse(NetworkResponse response)
// que devuelva un String
// Usa una expresión switch con coincidencia de patrones (pattern matching) para manejar cada caso:
// - Para Loading: devuelve "Status: [message]"
// - Para Success: devuelve "Success ([statusCode]): [data]"
// - Para Error: devuelve "Error ([errorCode]): [errorMessage]"
void main() {
// TODO: Crea una instancia de Loading con el mensaje "Please wait..."
// TODO: Crea una instancia de Success con los datos "User profile loaded" y el código de estado 200
// TODO: Crea una instancia de Error con el mensaje de error "Not found" y el código de error 404
// TODO: Imprime el resultado de manejar cada respuesta
}
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