Menu
Coddy logo textTech

Null Safety em Classes

Parte da seção Programação Orientada a Objetos do Journey de Dart da Coddy — lição 28 de 110.

Ao projetar classes, você precisa decidir quais campos são obrigatórios e quais são opcionais. A segurança de nulo (null safety) afeta como você declara variáveis de instância e escreve construtores.

Campos não anuláveis devem ser inicializados antes que o corpo do construtor seja executado. Você pode fazer isso por meio de listas de inicialização, parâmetros do construtor ou valores padrão:

class User {
  String name;           // Deve ser inicializado
  String? bio;           // Opcional - pode ser nulo
  int age = 0;           // Possui valor padrão
  
  User(this.name);       // name é inicializado via parâmetro
}

Para campos anuláveis, você pode deixá-los não inicializados, já que o padrão deles é null. Isso é útil para dados opcionais:

class Product {
  String title;
  String? description;   // Descrição opcional do produto
  double? discount;      // Sem desconto por padrão
  
  Product(this.title);
}

var item = Product('Laptop');
print(item.description);  // null
print(item.discount);     // null

Ao acessar campos anuláveis, use os operadores de reconhecimento de nulo (null-aware) que você aprendeu:

class Order {
  String id;
  User? customer;
  
  Order(this.id);
  
  String getCustomerName() {
    return customer?.name ?? 'Guest';
  }
}

A palavra-chave required em parâmetros nomeados garante que os chamadores forneçam valores essenciais:

class Account {
  String email;
  String? phone;
  
  Account({required this.email, this.phone});
}

var acc = Account(email: 'test@mail.com');  // phone é opcional

Ao escolher criteriosamente entre campos anuláveis e não anuláveis, você cria classes que comunicam claramente quais dados são essenciais e quais são opcionais.

challenge icon

Desafio

Fácil

Vamos construir um sistema de reservas para um hotel que demonstra como projetar classes com uma mistura cuidadosa de campos obrigatórios e opcionais usando null safety.

Você organizará seu código em dois arquivos:

  • reservation.dart: Defina uma classe Reservation que representa uma reserva de hotel. Toda reserva precisa de certas informações essenciais, mas alguns detalhes são extras opcionais que os hóspedes podem ou não fornecer. Sua classe deve ter:
    • Um String guestName não anulável - toda reserva deve ter o nome de um hóspede
    • Um int roomNumber não anulável - toda reserva deve ter um quarto atribuído
    • Um int nights não anulável com um valor padrão de 1 - a estadia mínima é de uma noite
    • Um String? specialRequests anulável - os hóspedes podem ter pedidos especiais
    • Um DateTime? checkInTime anulável - o horário exato de chegada pode não ser conhecido
    • Um construtor usando parâmetros nomeados com required para guestName e roomNumber, enquanto nights deve ser opcional com seu valor padrão
    • Um método getRequestsSummary() que retorna os pedidos especiais se fornecidos, ou 'No special requests' usando o operador ??
    • Um método getArrivalInfo() que retorna a representação em string do horário de check-in usando ?., ou null se não estiver definido
    • Um método printConfirmation() que exibe os detalhes da reserva
  • main.dart: Importe sua classe de reserva e crie reservas que mostrem diferentes combinações de dados obrigatórios e opcionais:
    • Crie uma reserva para o hóspede 'Sarah Johnson' no quarto 101 por 3 noites, com pedidos especiais 'Late checkout' e horário de check-in definido como DateTime(2024, 6, 15, 14, 30)
    • Crie uma reserva para o hóspede 'Mike Chen' no quarto 205 (use o padrão de noites), com apenas pedidos especiais definidos como 'Extra pillows'
    • Crie uma reserva para a hóspede 'Emma Davis' no quarto 310 por 2 noites (deixe ambos os campos opcionais como nulos)
    • Para cada reserva em ordem, chame printConfirmation(), e então imprima Arrival: [result] usando getArrivalInfo()

O método printConfirmation() deve imprimir neste formato:

=== Reservation Confirmed ===
Guest: [guestName]
Room: [roomNumber]
Nights: [nights]
Requests: [getRequestsSummary()]

Saída 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: null

Folha de consulta

Campos não anuláveis devem ser inicializados antes da execução do corpo do construtor, enquanto campos anuláveis podem permanecer não inicializados (o padrão é null):

class User {
  String name;           // Deve ser inicializado
  String? bio;           // Opcional - o padrão é null
  int age = 0;           // Possui valor padrão
  
  User(this.name);
}

Use campos anuláveis para dados opcionais:

class Product {
  String title;
  String? description;   // Opcional
  double? discount;      // Opcional
  
  Product(this.title);
}

var item = Product('Laptop');
print(item.description);  // null

Acesse campos anuláveis com operadores null-aware:

class Order {
  String id;
  User? customer;
  
  Order(this.id);
  
  String getCustomerName() {
    return customer?.name ?? 'Guest';
  }
}

Use a palavra-chave required em parâmetros nomeados para valores essenciais:

class Account {
  String email;
  String? phone;
  
  Account({required this.email, this.phone});
}

var acc = Account(email: 'test@mail.com');

Experimente você mesmo

import 'reservation.dart';

void main() {
  // TODO: Criar reserva para 'Sarah Johnson'
  // Quarto 101, 3 noites, pedidos especiais: 'Late checkout'
  // Horário de check-in: DateTime(2024, 6, 15, 14, 30)
  
  // TODO: Criar reserva para 'Mike Chen'
  // Quarto 205, usar noites padrão, pedidos especiais: 'Extra pillows'
  // Nenhum horário de check-in especificado
  
  // TODO: Criar reserva para 'Emma Davis'
  // Quarto 310, 2 noites, sem pedidos especiais, sem horário de check-in
  
  // TODO: Para cada reserva em ordem:
  // 1. Chamar printConfirmation()
  // 2. Imprimir 'Arrival: [result]' usando getArrivalInfo()
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos