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 no anulables deben inicializarse antes de que se ejecute el cuerpo del constructor. Puedes hacerlo mediante listas de inicializadores, 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 anulables y no anulables, creas clases que comunican claramente qué datos son esenciales y cuáles son opcionales.
Desafío
FácilConstruyamos un sistema de reservas para un hotel que demuestre cómo diseñar clases con una combinación bien pensada de campos obligatorios y opcionales mediante la seguridad frente a valores nulos.
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 proporcionar o no. Tu clase debe tener:- Un
String guestNameno anulable: toda reserva debe tener el nombre de un huésped - Un
int roomNumberno anulable: toda reserva debe tener una habitación asignada - Un
int nightsno anulable con un valor predeterminado de1: la estancia mínima es de una noche - Un
String? specialRequestsanulable: los huéspedes pueden tener solicitudes especiales - Un
DateTime? checkInTimeanulable: es posible que no se conozca la hora exacta de llegada - Un constructor que use parámetros con nombre y
requiredparaguestNameyroomNumber, mientras quenightsdebe ser opcional con su valor predeterminado - Un método
getRequestsSummary()que devuelva las solicitudes especiales si se proporcionaron, o'No special requests'mediante el operador?? - Un método
getArrivalInfo()que devuelva la representación de cadena de la hora de llegada mediante?., onullsi no se estableció - 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 las solicitudes especiales'Late checkout'y la hora de llegada establecida enDateTime(2024, 6, 15, 14, 30) - Crea una reserva para el huésped
'Mike Chen'en la habitación205(usa las noches predeterminadas), estableciendo únicamente las solicitudes especiales en'Extra pillows' - Crea una reserva para el huésped
'Emma Davis'en la habitación310por2noches (deja ambos campos opcionales como null) - Para cada reserva, en orden, llama a
printConfirmation()y después imprimeArrival: [result]usandogetArrivalInfo()
- Crea una reserva para el huésped
El método printConfirmation() debe imprimir con 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: nullPrué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 RepositoryPractica por tu cuenta: Compilador de Dart online