Null Safety en clases
Parte de la sección Programación Orientada a Objetos del Journey de Dart de Coddy — lección 28 de 110.
Al diseñar clases, debes decidir qué campos son obligatorios y cuáles son opcionales. La seguridad contra nulos (null safety) afecta cómo declaras las variables de instancia y escribes los constructores.
Los campos que no admiten valores nulos deben inicializarse antes de que se ejecute el cuerpo del constructor. Puedes hacerlo mediante listas de inicialización, parámetros del constructor o valores predeterminados:
class User {
String name; // Debe ser inicializado
String? bio; // Opcional - puede ser nulo
int age = 0; // Tiene un valor por defecto
User(this.name); // name se inicializa a través del parámetro
}Para los campos que admiten valores nulos, puedes dejarlos sin inicializar ya que por defecto son null. Esto es útil para datos opcionales:
class Product {
String title;
String? description; // Descripción opcional del producto
double? discount; // Sin descuento por defecto
Product(this.title);
}
var item = Product('Laptop');
print(item.description); // null
print(item.discount); // nullAl acceder a campos que admiten valores nulos, utiliza los operadores null-aware que has aprendido:
class Order {
String id;
User? customer;
Order(this.id);
String getCustomerName() {
return customer?.name ?? 'Guest';
}
}La palabra clave required en parámetros nombrados asegura que quienes realizan la llamada proporcionen valores esenciales:
class Account {
String email;
String? phone;
Account({required this.email, this.phone});
}
var acc = Account(email: 'test@mail.com'); // phone es opcionalAl elegir cuidadosamente entre campos nullable y non-nullable, creas clases que comunican claramente qué datos son esenciales y cuáles son opcionales.
Desafío
FácilVamos a construir un sistema de reservas para un hotel que demuestre cómo diseñar clases con una mezcla reflexiva de campos obligatorios y opcionales utilizando null safety.
Organizarás tu código en dos archivos:
reservation.dart: Define una claseReservationque represente una reserva de hotel. Cada reserva necesita cierta información esencial, pero algunos detalles son extras opcionales que los huéspedes pueden o no proporcionar. Tu clase debe tener:- Un
String guestNameno nulable: cada reserva debe tener un nombre de huésped. - Un
int roomNumberno nulable: a cada reserva se le debe asignar una habitación. - Un
int nightsno nulable con un valor por defecto de1: la estancia mínima es de una noche. - Un
String? specialRequestsnulable: los huéspedes pueden tener peticiones especiales. - Un
DateTime? checkInTimenulable: la hora exacta de llegada podría no conocerse. - Un constructor que utilice parámetros nombrados con
requiredparaguestNameyroomNumber, mientras quenightsdebe ser opcional con su valor por defecto. - Un método
getRequestsSummary()que devuelva las peticiones especiales si se proporcionan, o'No special requests'utilizando el operador??. - Un método
getArrivalInfo()que devuelva la representación en cadena de la hora de entrada utilizando?., onullsi no está establecida. - Un método
printConfirmation()que muestre los detalles de la reserva.
- Un
main.dart: Importa tu clase de reserva y crea reservas que muestren diferentes combinaciones de datos obligatorios y opcionales:- Crea una reserva para el huésped
'Sarah Johnson'en la habitación101por3noches, con peticiones especiales'Late checkout'y la hora de entrada establecida enDateTime(2024, 6, 15, 14, 30). - Crea una reserva para el huésped
'Mike Chen'en la habitación205(usa las noches por defecto), con solo las peticiones especiales establecidas en'Extra pillows'. - Crea una reserva para el huésped
'Emma Davis'en la habitación310por2noches (deja ambos campos opcionales como nulos). - Para cada reserva en orden, llama a
printConfirmation(), luego imprimeArrival: [result]usandogetArrivalInfo().
- Crea una reserva para el huésped
El método printConfirmation() debe imprimir en este formato:
=== Reservation Confirmed ===
Guest: [guestName]
Room: [roomNumber]
Nights: [nights]
Requests: [getRequestsSummary()]Salida esperada:
=== Reservation Confirmed ===
Guest: Sarah Johnson
Room: 101
Nights: 3
Requests: Late checkout
Arrival: 2024-06-15 14:30:00.000
=== Reservation Confirmed ===
Guest: Mike Chen
Room: 205
Nights: 1
Requests: Extra pillows
Arrival: null
=== Reservation Confirmed ===
Guest: Emma Davis
Room: 310
Nights: 2
Requests: No special requests
Arrival: nullHoja de referencia
Los campos no anulables deben inicializarse antes de que se ejecute el cuerpo del constructor, mientras que los campos anulables pueden permanecer sin inicializar (por defecto a null):
class User {
String name; // Debe ser inicializado
String? bio; // Opcional - por defecto es null
int age = 0; // Tiene un valor por defecto
User(this.name);
}Utilice campos anulables para datos opcionales:
class Product {
String title;
String? description; // Opcional
double? discount; // Opcional
Product(this.title);
}
var item = Product('Laptop');
print(item.description); // nullAcceda a los campos anulables con operadores que detectan nulos (null-aware):
class Order {
String id;
User? customer;
Order(this.id);
String getCustomerName() {
return customer?.name ?? 'Guest';
}
}Utilice la palabra clave required en parámetros con nombre para valores esenciales:
class Account {
String email;
String? phone;
Account({required this.email, this.phone});
}
var acc = Account(email: 'test@mail.com');Pruébalo tú mismo
import 'reservation.dart';
void main() {
// TODO: Crear reserva para 'Sarah Johnson'
// Habitación 101, 3 noches, peticiones especiales: 'Late checkout'
// Hora de check-in: DateTime(2024, 6, 15, 14, 30)
// TODO: Crear reserva para 'Mike Chen'
// Habitación 205, usar noches por defecto, peticiones especiales: 'Extra pillows'
// No se especificó hora de check-in
// TODO: Crear reserva para 'Emma Davis'
// Habitación 310, 2 noches, sin peticiones especiales, sin hora de check-in
// TODO: Para cada reserva en orden:
// 1. Llamar a printConfirmation()
// 2. Imprimir 'Arrival: [result]' usando getArrivalInfo()
}
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