Как Generics (обобщения) связаны с полиморфизмом в Dart?

«Как Generics (обобщения) связаны с полиморфизмом в Dart?» — вопрос из категории ООП, который задают на 36% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Generics в Dart — это реализация параметрического полиморфизма. Они позволяют писать код, который может работать с разными типами данных, оставаясь типобезопасным. Один алгоритм или структура данных становится полиморфной по отношению к типу своих элементов.

Без Generics (отсутствие полиморфизма или небезопасно):

// 1. Код дублируется для каждого типа (нет полиморфизма).
class IntBox { int value; IntBox(this.value); }
class StringBox { String value; StringBox(this.value); }

// 2. Использование dynamic/Object (полиморфно, но небезопасно).
class DynamicBox {
  dynamic value; // Потеряна безопасность типов.
  DynamicBox(this.value);
}

С Generics (параметрический полиморфизм с безопасностью типов):

// Один полиморфный класс для множества типов.
class Box<T> {
  final T value;
  Box(this.value);

  T getValue() => value;
}

void main() {
  // Компилятор выводит конкретный тип для каждого экземпляра.
  final intBox = Box<int>(42); // Box<int>
  final stringBox = Box('Hello'); // Box<String> (тип выведен)

  // Безопасность типов сохраняется.
  int number = intBox.getValue(); // OK
  // String text = intBox.getValue(); // Ошибка компиляции: type 'int' is not a subtype of 'String'
}

Связь с другими видами полиморфизма в Dart:

  • Полиморфизм подтипов (наследование): class SiberianHusky extends DogSiberianHusky может использоваться везде, где ожидается Dog.
  • Параметрический полиморфизм (Generics): List<Dog> может содержать SiberianHusky, Beagle и т.д., потому что они — подтипы Dog. Это показывает, как Generics взаимодействуют с полиморфизмом подтипов.

Практический пример из Flutter:

// Виджет FutureBuilder полиморфен относительно типа данных Future.
FutureBuilder<List<Post>>(
  future: _fetchPosts(), // Future<List<Post>>
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      // snapshot.data имеет тип List<Post>? благодаря generics.
      return ListView.builder(
        itemCount: snapshot.data!.length,
        itemBuilder: (ctx, i) => PostWidget(snapshot.data![i]), // Тип Post известен.
      );
    }
    return CircularProgressIndicator();
  },
)

Таким образом, Generics делают код полиморфным (работает со многими типами), строго типизированным (ошибки ловятся на этапе компиляции) и избегающим дублирования.