Generics - Обобщённые типы
Что такое Generics?
Generics позволяют писать гибкий и безопасный код, который может работать с разными типами данных без дублирования кода.
Это как универсальный шаблон, в который можно подставить любой тип, и Dart автоматически будет следить за правильностью типов.
Улучшение читаемости и безопасности кода
Допустим, у нас есть список List. По умолчанию в него можно добавить что угодно.
Проблема без Generics:
Dart - Список без Generics
void main() {
List data = []; // Нет ограничений на тип данных
data.add(42);
data.add("Dart");
data.add(true);
}
Решение с Generics:
В треугольных скобках < > указываем нужный тип данных
Dart - Список с Generics
void main() {
List<String> names = []; // Теперь в список можно добавлять только строки
names.add("Dart");
names.add("Flutter");
// names.add(42); // Ошибка: Нельзя добавить число в список
print(names); // [Dart, Flutter]
}
Теперь Dart заставляет следовать правилам типов и предотвращает ошибки.
Избегаем дублирования кода
Представь, что мы пишем систему кеширования данных.
Без Generics нам пришлось бы делать отдельный класс для каждого типа данных:
Много лишнего кода
Dart - Дублирование кода
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
abstract class IntCache {
int getByKey(String key);
void setByKey(String key, int value);
}
Используем Generics и избавляемся от дублирования
Dart - Универсальный класс с Generics
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
Теперь можно создать универсальный кеш, который хранит любой тип:
Вместо T внутри скобок <T> будет подставляться тип который мы укажем при вызове
Dart - Реализация универсального кеша
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
class MyIntCache implements Cache<int> {
Map<String, int> _cache = {};
@override
int getByKey(String key) => _cache[key] ?? 0;
@override
void setByKey(String key, int value) {
_cache[key] = value;
}
}
class MyStringCache implements Cache<String> {
Map<String, String> _cache = {};
@override
String getByKey(String key) => _cache[key] ?? '';
@override
void setByKey(String key, String value) {
_cache[key] = value;
}
}
void main() {
final intCache = MyIntCache();
intCache.setByKey('age', 18);
print(intCache.getByKey('age')); // 30
final stringCache = MyStringCache();
stringCache.setByKey('name', 'Наташа');
print(stringCache.getByKey('name')); // John
}
Теперь неважно, что мы кешируем (строки, числа, объекты) — один класс справится со всеми случаями!
Реализация стека (Stack)
Сделаем универсальный стек, который работает с любыми типами
Dart - Универсальный стек
class Stack<T> {
List<T> _storage = [];
void push(T item) => _storage.add(item);
T pop() => _storage.removeLast();
}
void main() {
var stackInt = Stack<int>();
stackInt.push(42);
print(stackInt.pop()); // 42
var stackString = Stack<String>();
stackString.push("Hello");
print(stackString.pop()); // "Hello"
}
Generics в методах
Можно использовать Generics не только в классах, но и в методах.
Этот метод работает с любыми типами и автоматически подставляет нужный тип
Dart - Generic-метод
T first<T>(List<T> list) {
return list[0]; // Возвращаем первый элемент списка
}
void main() {
print(first([1, 2, 3])); // 1
print(first(["A", "B", "C"])); // "A"
}
Ограничение типов с extends
Иногда Generics должны работать только с определёнными типами.
Например, мы хотим, чтобы класс мог работать только с наследниками BaseClass.
Теперь ChildClass<T> может работать только с BaseClass и его наследниками.
Dart - Ограничение типов
class BaseClass {}
class ChildClass<T extends BaseClass> {
String toString() => "Экземпляр класса 'ChildClass<$T>'";
}
class Extender extends BaseClass {}
void main() {
var obj1 = ChildClass<BaseClass>(); // Работает
var obj2 = ChildClass<Extender>(); // Работает
var obj3 = ChildClass<int>(); // Ошибка: int не является BaseClass
}