Menu
Coddy logo textTech

Null-безопасность в классах

Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy — урок 28 из 110.

При проектировании классов вам нужно решить, какие поля являются обязательными, а какие — необязательными. Null safety влияет на то, как вы объявляете переменные экземпляра и пишете конструкторы.

Поля, не допускающие значения null, должны быть инициализированы до выполнения тела конструктора. Это можно сделать с помощью списков инициализации, параметров конструктора или значений по умолчанию:

class User {
  String name;           // Должно быть инициализировано
  String? bio;           // Необязательно — может быть null
  int age = 0;           // Имеет значение по умолчанию
  
  User(this.name);       // name инициализируется через параметр
}

Для полей, допускающих значение null, вы можете оставить их неинициализированными, так как по умолчанию они принимают значение null. Это полезно для необязательных данных:

class Product {
  String title;
  String? description;   // Необязательное описание продукта
  double? discount;      // По умолчанию скидка отсутствует
  
  Product(this.title);
}

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

При обращении к полям, допускающим значение null, используйте изученные вами операторы для работы с null:

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

Ключевое слово required в именованных параметрах гарантирует, что вызывающая сторона предоставит необходимые значения:

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

var acc = Account(email: 'test@mail.com');  // phone необязателен

Продуманно выбирая между nullable и non-nullable полями, вы создаете классы, которые четко сообщают, какие данные являются обязательными, а какие — необязательными.

challenge icon

Задание

Легко

Давайте создадим систему бронирования для отеля, которая продемонстрирует, как проектировать классы с продуманным сочетанием обязательных и необязательных полей, используя безопасность типов по отношению к null (null safety).

Вы организуете свой код в двух файлах:

  • reservation.dart: Определите класс Reservation, который представляет бронирование отеля. Каждому бронированию необходима определенная важная информация, но некоторые детали являются необязательными дополнениями, которые гости могут предоставлять или не предоставлять. Ваш класс должен иметь:
    • Не допускающую null (non-nullable) переменную String guestName — каждое бронирование должно содержать имя гостя
    • Не допускающую null (non-nullable) переменную int roomNumber — каждому бронированию должен быть назначен номер комнаты
    • Не допускающую null (non-nullable) переменную int nights со значением по умолчанию 1 — минимальный срок проживания составляет одну ночь
    • Допускающую null (nullable) переменную String? specialRequests — у гостей могут быть особые пожелания
    • Допускающую null (nullable) переменную DateTime? checkInTime — точное время прибытия может быть неизвестно
    • Конструктор, использующий именованные параметры с ключевым словом required для guestName и roomNumber, в то время как nights должен быть необязательным со своим значением по умолчанию
    • Метод getRequestsSummary(), который возвращает особые пожелания, если они указаны, или 'No special requests', используя оператор ??
    • Метод getArrivalInfo(), который возвращает строковое представление времени регистрации, используя ?., или null, если оно не установлено
    • Метод printConfirmation(), который отображает детали бронирования
  • main.dart: Импортируйте ваш класс reservation и создайте бронирования, демонстрирующие различные комбинации обязательных и необязательных данных:
    • Создайте бронирование для гостя 'Sarah Johnson' в номере 101 на 3 ночи, с особыми пожеланиями 'Late checkout' и временем регистрации, установленным на DateTime(2024, 6, 15, 14, 30)
    • Создайте бронирование для гостя 'Mike Chen' в номере 205 (используйте количество ночей по умолчанию), указав только особые пожелания 'Extra pillows'
    • Создайте бронирование для гостя 'Emma Davis' в номере 310 на 2 ночи (оставьте оба необязательных поля как null)
    • Для каждого бронирования по порядку вызовите printConfirmation(), затем напечатайте Arrival: [result], используя getArrivalInfo()

Метод printConfirmation() должен выводить данные в следующем формате:

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

Ожидаемый вывод:

=== 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

Шпаргалка

Поля, не допускающие значения null (non-nullable), должны быть инициализированы до выполнения тела конструктора, в то время как поля, допускающие значения null (nullable), могут оставаться неинициализированными (по умолчанию принимая значение null):

class User {
  String name;           // Должно быть инициализировано
  String? bio;           // Необязательно — по умолчанию null
  int age = 0;           // Имеет значение по умолчанию
  
  User(this.name);
}

Используйте поля, допускающие значение null, для необязательных данных:

class Product {
  String title;
  String? description;   // Необязательно
  double? discount;      // Необязательно
  
  Product(this.title);
}

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

Обращайтесь к полям, допускающим значение null, с помощью операторов осведомленности о null (null-aware operators):

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

Используйте ключевое слово required в именованных параметрах для обязательных значений:

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

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

Попробуйте сами

import 'reservation.dart';

void main() {
  // TODO: Создать бронирование для 'Sarah Johnson'
  // Номер 101, 3 ночи, особые пожелания: 'Late checkout'
  // Время заезда: DateTime(2024, 6, 15, 14, 30)
  
  // TODO: Создать бронирование для 'Mike Chen'
  // Номер 205, использовать количество ночей по умолчанию, особые пожелания: 'Extra pillows'
  // Время заезда не указано
  
  // TODO: Создать бронирование для 'Emma Davis'
  // Номер 310, 2 ночи, без особых пожеланий, без времени заезда
  
  // TODO: Для каждого бронирования по порядку:
  // 1. Вызвать printConfirmation()
  // 2. Вывести 'Arrival: [result]', используя getArrivalInfo()
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование