← Назад к вопросам

Как Generics связаны с полиморфизмом?

2.0 Middle🔥 161 комментариев
#Dart

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Generics и их связь с полиморфизмом в Dart

Generics (Обобщённое программирование) — это механизм, позволяющий создавать код, который работает с различными типами, сохраняя типобезопасность. Это форма параметрического полиморфизма, которая тесно связана с полиморфизмом в целом.

Что такое Generics

Generics позволяют определять классы, интерфейсы и функции, работающие с типами как с параметрами.

// Простой пример Generic класса
class Box<T> {
  T _value;
  
  Box(this._value);
  
  T get value => _value;
  
  void set value(T newValue) {
    _value = newValue;
  }
}

void main() {
  // Box для String
  final stringBox = Box<String>("Hello");
  print(stringBox.value); // Hello
  
  // Box для int
  final intBox = Box<int>(42);
  print(intBox.value); // 42
  
  // Box для Custom класса
  final userBox = Box<User>(User("John", 30));
  print(userBox.value.name); // John
  
  // Ошибка типа на этапе компиляции
  // stringBox.value = 123; // Ошибка: int не совместим с String
}

Связь между Generics и полиморфизмом

Generics — это форма полиморфизма, называемая параметрическим полиморфизмом. Основные типы полиморфизма:

// 1. ПАРАМЕТРИЧЕСКИЙ ПОЛИМОРФИЗМ (Generics)
// Одна функция работает с разными типами
T identity<T>(T value) => value;

print(identity<int>(42)); // 42
print(identity<String>("hello")); // hello
print(identity<bool>(true)); // true

// 2. ПОДТИПНЫЙ ПОЛИМОРФИЗМ (Inheritance & Interface)
// Разные реализации одного интерфейса
abstract class Animal {
  void makeSound();
}

class Dog implements Animal {
  @override
  void makeSound() => print("Woof!");
}

class Cat implements Animal {
  @override
  void makeSound() => print("Meow!");
}

void playWithAnimal(Animal animal) {
  animal.makeSound();
}

playWithAnimal(Dog()); // Woof!
playWithAnimal(Cat()); // Meow!

Generic классы и наследование

// Generic базовый класс
class Repository<T> {
  List<T> _items = [];
  
  void add(T item) => _items.add(item);
  T? getById(int index) => index < _items.length ? _items[index] : null;
  List<T> getAll() => List.unmodifiable(_items);
}

// Специализированный репозиторий через наследование
class UserRepository extends Repository<User> {
  User? findByEmail(String email) {
    return getAll().firstWhere(
      (user) => user.email == email,
      orElse: () => throw Exception("User not found"),
    );
  }
}

class PostRepository extends Repository<Post> {
  List<Post> findByAuthor(String authorId) {
    return getAll()
        .where((post) => post.authorId == authorId)
        .toList();
  }
}

void main() {
  final userRepo = UserRepository();
  userRepo.add(User("John", "john@example.com"));
  
  final postRepo = PostRepository();
  postRepo.add(Post("Hello", "1"));
}

Generic функции и методы

// Generic функция
T findMax<T extends Comparable<T>>(List<T> items) {
  return items.reduce((a, b) => a.compareTo(b) > 0 ? a : b);
}

print(findMax<int>([1, 5, 3, 9, 2])); // 9
print(findMax<String>(["apple", "zebra", "banana"])); // zebra

// Generic метод
class Sorter {
  List<T> sort<T extends Comparable<T>>(List<T> items) {
    final sorted = [...items];
    sorted.sort();
    return sorted;
  }
}

final sorter = Sorter();
print(sorter.sort<int>([3, 1, 4, 1, 5])); // [1, 1, 3, 4, 5]

Ограничения типов (Type Bounds)

// Ограничение: T должен быть наследником Comparable
T maximum<T extends Comparable<T>>(List<T> list) {
  return list.reduce((a, b) => a.compareTo(b) > 0 ? a : b);
}

// Ограничение: T должен быть наследником Animal
class Zoo<T extends Animal> {
  List<T> animals = [];
  
  void addAnimal(T animal) {
    animals.add(animal);
  }
  
  void feedAll() {
    for (final animal in animals) {
      animal.eat(); // T гарантировано имеет метод eat()
    }
  }
}

class Dog extends Animal {
  @override
  void eat() => print("Dog eating");
}

void main() {
  final dogZoo = Zoo<Dog>();
  dogZoo.addAnimal(Dog());
  dogZoo.feedAll(); // Dog eating
}

Variance в Generics

// Covariance (ковариантность): стрелка совпадает
// Если Dog является Animal, то List<Dog> может быть List<Animal>
abstract class Container<out T> {
  T get();
}

class DogContainer implements Container<Dog> {
  @override
  Dog get() => Dog();
}

void consumeContainer(Container<Animal> container) {
  Animal animal = container.get();
  animal.makeSound();
}

void main() {
  consumeContainer(DogContainer()); // OK благодаря covariance
}

// Contravariance (контравариантность): стрелка противоположна
abstract class Producer<in T> {
  void produce(T item);
}

class AnimalProducer implements Producer<Animal> {
  @override
  void produce(Animal animal) {
    print("Produced animal");
  }
}

void provideToProducer(Producer<Dog> producer) {
  // producer.produce(Dog());
}

void main() {
  // provideToProducer(AnimalProducer()); // ERROR: contravariance
}

Практические примеры во Flutter

Generic API клиент:

class ApiClient {
  Future<T> get<T>(String url, T Function(Map<String, dynamic>) fromJson) async {
    final response = await http.get(Uri.parse(url));
    if (response.statusCode != 200) {
      throw Exception("API Error: ${response.statusCode}");
    }
    final json = jsonDecode(response.body) as Map<String, dynamic>;
    return fromJson(json);
  }
  
  Future<List<T>> getList<T>(
    String url,
    T Function(Map<String, dynamic>) fromJson,
  ) async {
    final response = await http.get(Uri.parse(url));
    if (response.statusCode != 200) {
      throw Exception("API Error: ${response.statusCode}");
    }
    final json = jsonDecode(response.body) as List;
    return json.map((item) => fromJson(item as Map<String, dynamic>)).toList();
  }
}

class User {
  final String id, name;
  User({required this.id, required this.name});
  
  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json["id"],
      name: json["name"],
    );
  }
}

void main() async {
  final client = ApiClient();
  
  final user = await client.get<User>(
    "https://api.example.com/users/1",
    User.fromJson,
  );
  print(user.name);
  
  final users = await client.getList<User>(
    "https://api.example.com/users",
    User.fromJson,
  );
  print(users.length);
}

Generic State Management:

abstract class Repository<T> {
  Future<T> fetch();
  Future<void> save(T data);
}

class AsyncValueNotifier<T> extends ValueNotifier<AsyncValue<T>> {
  AsyncValueNotifier(this.repository)
      : super(const AsyncValue.loading());
  
  final Repository<T> repository;
  
  Future<void> load() async {
    value = const AsyncValue.loading();
    try {
      final data = await repository.fetch();
      value = AsyncValue.data(data);
    } catch (e, st) {
      value = AsyncValue.error(e, st);
    }
  }
}

class AsyncValue<T> {
  const AsyncValue();
  
  const factory AsyncValue.loading() = _LoadingAsyncValue;
  const factory AsyncValue.data(T data) = _DataAsyncValue;
  const factory AsyncValue.error(Object error, StackTrace stackTrace) =
      _ErrorAsyncValue;
}

class _LoadingAsyncValue<T> extends AsyncValue<T> {
  const _LoadingAsyncValue();
}

class _DataAsyncValue<T> extends AsyncValue<T> {
  final T data;
  const _DataAsyncValue(this.data);
}

class _ErrorAsyncValue<T> extends AsyncValue<T> {
  final Object error;
  final StackTrace stackTrace;
  const _ErrorAsyncValue(this.error, this.stackTrace);
}

Преимущества Generics

Типобезопасность — ошибки типов выявляются на этапе компиляции ✅ Переиспользование кода — одна реализация для разных типов ✅ Производительность — избегаем боксинга/анбоксинга ✅ Читаемость — явно видна работа с типами ✅ Полиморфизм — достигаем гибкости без потери типобезопасности

Заключение

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

Как Generics связаны с полиморфизмом? | PrepBro