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 полями, вы создаете классы, которые четко сообщают, какие данные являются обязательными, а какие — необязательными.
Задание
ЛегкоДавайте создадим систему бронирования для отеля, которая продемонстрирует, как проектировать классы с продуманным сочетанием обязательных и необязательных полей, используя безопасность типов по отношению к 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(), который отображает детали бронирования
- Не допускающую null (non-nullable) переменную
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()
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыБиблиотеки и импортВведение в ООПКлассы и объектыКлючевое слово thisМетодыПеременные экземпляраОсновы конструкторовИтоги — Простой калькулятор4Null-безопасность
Введение в Null-безопасностьNullable и Non-Nullable типыОператоры ? и !Ключевое слово Late и Null-безопасностьNull-aware операторыNull-безопасность в классахИтоги — Система профилей пользователей7Абстрактные классы и интерфейсы
Абстрактные классыАбстрактные методыИнтерфейсы в DartНеявные интерфейсыРеализация против наследованияМножественные интерфейсыИтоги — Калькулятор фигур10Коллекции и обобщения
Обзор List, Set, MapТипобезопасные коллекцииОбобщенные классыОбобщенные методыОграничения обобщенийIterable и IteratorИтоги: Обобщенное хранилище13Продвинутые концепции ООП
Композиция против наследованияМетоды расширенияВызываемые классыЗапечатанные классы (Dart 3)Записи (Dart 3)Паттерны и сопоставление (3.0)Перечисления с методами16Проект: Управление библиотекой
Обзор проектаКлассы Book и User2Конструкторы в Dart
Конструктор по умолчаниюИменованные конструкторыСписки инициализацииКонстантные конструкторыФабричные конструкторыПеренаправляющие конструкторыИтоги — Shape Builder5Инкапсуляция
Публичные и приватные членыСоглашение о префиксе _Приватность на уровне библиотекГеттеры и сеттеры: подробный разборСкрытие данныхИтоги — Записи о студентах8Mixins
Введение в MixinsСоздание MixinsИспользование нескольких MixinsКлючевое слово on в MixinsMixin и наследованиеMixin и интерфейсИтоги: Система животных11Специальные методы
Переопределение toString()Переопределение hashCode и ==Интерфейс ComparableМетод call()Переопределение noSuchMethodПовторение — Пользовательская коллекция14Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy3Свойства классов
Члены экземпляра и статические членыПоля Final и ConstПеременные LateСтатические методы и поляГеттеры и сеттерыИтоги — Менеджер банковского счета6Наследование
Основы наследованияКлючевое слово superПереопределение методовАннотация @overrideКлючевое слово final для классовКонструкторы и наследованиеИтоги: Иерархия сотрудников9Полиморфизм
Основы полиморфизмаПолиморфизм через интерфейсыПроверка типов во время выполненияОператоры is и asКлючевое слово covariantИтоги — Обработчик платежей12Асинхронное ООП
Futures и async/awaitОсновы StreamsКонтроллеры StreamАсинхронные конструкторыАсинхронность в методах классовИтоги — Data Fetcher15Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий