Ответ
Sealed class (запечатанный класс) в Dart — это класс, который явно ограничивает своё наследование. Все его прямые подтипы должны быть объявлены в том же файле (и библиотеке), что и сам sealed class. Это создаёт замкнутую иерархию типов, известную компилятору, что позволяет использовать исчерпывающий (exhaustive) pattern matching.
Основная цель: Моделирование дискретных, известных на этапе компиляции вариантов, таких как состояния загрузки, результаты операций или узлы AST.
Пример: Моделирование результата сетевого запроса
// Все возможные подтипы Result определены здесь и только здесь.
sealed class Result<T> {}
class Success<T> extends Result<T> {
final T data;
Success(this.data);
}
class Error<T> extends Result<T> {
final Exception exception;
Error(this.exception);
}
class Loading<T> extends Result<T> {}
// Использование с исчерпывающим switch
Widget buildResultWidget(Result<String> result) {
return switch (result) {
Success(data: final data) => Text('Данные: $data'),
Error(exception: final e) => Text('Ошибка: ${e.toString()}'),
Loading() => CircularProgressIndicator(),
// Компилятор знает, что все случаи покрыты. Если добавить новый подтип Result,
// switch выдаст ошибку компиляции, пока его не обработать.
};
}
Преимущества:
- Безопасность: Компилятор гарантирует обработку всех вариантов в
switchиif-case. - Читаемость: Структура данных явно документирована в коде.
- Удобство рефакторинга: При добавлении нового варианта компилятор укажет все места, которые нужно обновить.
Ответ 18+ 🔞
Э, слушай, а вот это sealed class в Dart — это вообще огонь, я тебе скажу. Представь себе такую ситуацию: у тебя есть какой-то тип, и ты заранее, как бог, знаешь, какие у него могут быть варианты. И ты говоришь компилятору: «Смотри, ёпта, вот этот класс — он запечатанный. Все его дети будут только вот в этом файле, и больше нихуя нигде». И компилятор тебе верит, потому что доверия ебать ноль ко всем остальным, и начинает мозговать.
Зачем это, спросишь? Ну, чтобы не было этих вечных «а вдруг кто-то в другом углу проекта наследуется и всё сломает». Ты моделируешь чёткие, дискретные состояния. Типа, результат запроса: либо успех, либо ошибка, либо грузится. Или узлы какого-нибудь дерева разбора. Всё просто и ясно, как божий день.
Смотри, как это выглядит на практике:
// Всё, приехали. Все наследники Result живут тут и только тут.
sealed class Result<T> {}
class Success<T> extends Result<T> {
final T data;
Success(this.data);
}
class Error<T> extends Result<T> {
final Exception exception;
Error(this.exception);
}
class Loading<T> extends Result<T> {}
Красота, да? Три варианта, и всё. Теперь самое вкусное — использование. Берёшь switch и просто пишешь:
Widget buildResultWidget(Result<String> result) {
return switch (result) {
Success(data: final data) => Text('Данные: $data'),
Error(exception: final e) => Text('Ошибка: ${e.toString()}'),
Loading() => CircularProgressIndicator(),
// Компилятор тут такой: «О, я всё вижу, всё случаи покрыты. Молодец, чувак».
// А если ты завтра добавишь новый подтип Result, он тебе сразу вьебет ошибку компиляции,
// пока не обработаешь и его. Никаких сюрпризов в рантайме!
};
}
В чём, блядь, соль-то?
- Безопасность на уровне компилятора. Это не тот случай, когда «ой, забыл кейс обработать». Компилятор сам тебе мозги поправит. Волнение ебать — нулевое.
- Читаемость пиздец. Открыл файл — и сразу видишь всю иерархию, как на ладони. Никаких «а этот класс откуда вылез, ядрёна вошь».
- Рефакторинг — одно удовольствие. Захотел добавить новый вариант типа
Partial<T>? Добавляй. Компилятор тут же начнёт орать на всехswitch, где его нет, и ты спокойно всё допишешь. Удивление пиздец, как же раньше без этого жили.
В общем, вещь. Когда нужно жёстко ограничить возможные состояния — это твой выбор. Чисто, надёжно, и компилятор за тебя думает. Э сабака сука, удобно же!
Видео-ответы
▶
▶
▶
▶
▶
▶
▶
▶
▶