Урок по треку Flutter и Dart 7 мин

Dart: классы, миксины, расширения и sealed

Конструкторы, factory, миксины, расширения, sealed и модификаторы классов Dart 3.

В Dart одиночное наследование, зато каждый класс неявно задаёт интерфейс, а поведение можно подмешивать миксинами. Dart 3 добавил модификаторы классов, и на собесе про них спрашивают всё чаще.

Конструкторы

class User {
  final String name;
  final int age;
  const User(this.name, {this.age = 0});          // const-конструктор
  User.guest() : this('гость');                   // именованный, перенаправляет
  factory User.fromJson(Map<String, dynamic> j) =>
      User(j['name'] as String, age: j['age'] as int);
}
  • Список инициализации (после :) выполняется до тела конструктора. Там задают final-поля, вызывают super(...) и пишут assert.
  • const-конструктор возможен, если все поля final. Вызов с const создаёт канонический экземпляр. Во Flutter это главный способ сэкономить перестроения: const Text('Привет') не пересоздаётся при каждом build.
  • factory не обязан создавать новый объект: может вернуть кешированный экземпляр или подкласс. Внутри нет this.
  • Параметры бывают позиционными, необязательными позиционными [int x = 0] и именованными {required String id}.

extends, implements, with

  • extends наследует реализацию, можно вызвать super.
  • implements берёт только интерфейс: все члены придётся реализовать самому. Реализовать можно любой класс, не только абстрактный.
  • with подмешивает миксин: его методы копируются в цепочку наследования.
mixin Logger {
  void log(String m) => print('[$runtimeType] $m');
}
mixin Retry on Api {           // применим только к наследникам Api
  Future<T> retry<T>(Future<T> Function() f) async { /* ... */ }
}
class UserApi extends Api with Logger, Retry {}

Линеаризация. class C extends A with M1, M2 строит цепочку A -> A+M1 -> A+M1+M2 -> C. Если метод есть и в M1, и в M2, побеждает тот, что правее (M2), а super внутри M2 указывает на M1. Это популярная задача «что выведет».

Во Flutter миксины на каждом шагу: SingleTickerProviderStateMixin, WidgetsBindingObserver, AutomaticKeepAliveClientMixin.

Модификаторы классов Dart 3

МодификаторСмысл
abstractнельзя создать экземпляр
interfaceвне библиотеки можно только implements, не extends
baseвне библиотеки можно только extends, не implements
finalвне библиотеки нельзя ни наследовать, ни реализовывать
sealedабстрактный, все подтипы в той же библиотеке, switch проверяет полноту
mixin classможно использовать и как класс, и как миксин

sealed идеален для состояний и результатов:

sealed class LoadState {}
class Loading extends LoadState {}
class Data extends LoadState { final List<String> items; Data(this.items); }
class Failure extends LoadState { final Object error; Failure(this.error); }

Widget view(LoadState s) => switch (s) {
  Loading() => const CircularProgressIndicator(),
  Data(:final items) => Text('${items.length}'),
  Failure(:final error) => Text('$error'),
};

Добавили новый подкласс: компилятор покажет все switch, где он не обработан.

Расширения

extension добавляет методы существующему типу без наследования:

extension on String {
  String get capitalized => isEmpty ? this : this[0].toUpperCase() + substring(1);
}

Расширения разрешаются статически, по объявленному типу переменной. На dynamic они не работают, и переопределить их в подклассе нельзя.

extension type (Dart 3.3) это обёртка над типом без затрат в рантайме: например, UserId поверх int, чтобы не перепутать с OrderId.

Равенство

По умолчанию == сравнивает ссылки. Для значимых объектов переопределяют == и hashCode вместе, иначе объект «теряется» в Set и Map. На практике это делают пакетами equatable или freezed, а records уже сравниваются по значению.

Частые ошибки

  • Переопределить == без hashCode.
  • Ждать полиморфизма от расширения: вызов зависит от статического типа.
  • Путать implements и extends: при implements реализация не наследуется.

Что спросят на собесе

  • Виды конструкторов, зачем factory и const.
  • Миксины: что такое on, как разрешаются конфликты методов.
  • Модификаторы классов Dart 3 и зачем sealed.
  • Чем расширение отличается от наследования.
  • Как правильно реализовать равенство объектов.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.