Дарт - невероятный и гибкий язык. Если вы пришли из строго типизированных и статических языков, таких как Java и C #, или из динамических и слабо типизированных языков, таких как JavaScript и Python, вы заметите, что Dart может вести себя почти так же, как вы знакомы. Это действительно потрясающе, и некоторые функции и концепции можно использовать на нескольких языках. Сегодня мы немного поговорим о Generics, одном из самых мощных инструментов Java и C #.
Если вы используете GetIt, Flutter Modular, GetX или даже List определенного типа данных, вы уже использовали Generics в Dart. Взгляните на следующий пример:
Modular.get<AppController>();
Таким образом мы получаем экземпляр зарегистрированного объекта с помощью Flutter Modular. Обратите внимание, что у нас есть статическая функция с именем get, у которой нет параметров. Вместо этого мы просто передаем тип класса внутри символов «меньше» и «больше». Это полезно, когда у нас есть разные типы данных в коллекции. Но как мы можем это сделать сами?
Представим, что вам нужно добавить элементы любого вида в список внутри класса. Как бы Вы это сделали? Вы можете создать класс и использовать дженерики, чтобы сообщить компилятору, что мы хотим иметь возможность хранить любые данные в списке.
class MyClass { var _myList = <dynamic>[]; }
Нам нужен метод для добавления нового элемента в этот список. Это просто, правда?
void add(dynamic element) => _myList.add(element);
Теперь представьте, что вам нужно восстановить данные определенного типа из этого списка. Подумайте об этом: как мы можем сказать компилятору, какой тип данных нам нужен? Мы не можем передавать тип в качестве параметра, как мы это делаем с переменными. Решение этой проблемы - ожидать тип с именем T (имя не имеет значения), а затем использовать его для сравнения элементов списка. Сигнатура нашего метода будет такой:
get<T>();
Когда мы не сообщаем компилятору тип возвращаемого значения, по умолчанию он будет динамическим. Допустим, у вас есть 3 объекта одного типа. В этом случае нам нужно вернуть список товаров. Мы узнаем тип желаемого элемента во время компиляции, и нам не нужно возвращать динамический тип. Мы можем изменить подпись на это:
List<T> get<T>();
Реализация этого метода очень проста: сначала мы проверяем, принадлежит ли элемент в списке к тому же типу, который мы передали в метод, T. Если эта оценка верна, мы добавляем элемент в список. Нам нужно выполнить эту проверку для всех элементов в _myList, а затем вернуть список элементов с типом T. Любые данные в Dart будут иметь атрибут с именем runtimeType. Как вы можете догадаться, этот атрибут возвращает тип элемента. Итак, наша реализация будет такой:
List<T> get<T>() { var elements = <T>[]; for (var element in _myList) { if (element.runtimeType == T) { elements.add(element); } } return elements; }
Удобно, что команда Dart включила метод в класс Iterable (который реализуется List), который имеет ту же цель. Наш код можно свести к:
List<T> get<T>() => _myList.whereType<T>().toList();
Да, так просто. Я добавил метод toList (), чтобы убедиться, что мы возвращаем список. С этими двумя методами наш класс закончен:
class MyClass { var _myList = <dynamic>[]; void add(dynamic element) => _myList.add(element); List<T> get<T>() => _myList.whereType<T>().toList(); }
Давайте протестируем наш код:
void main() { final instance = MyClass(); instance.add(1); instance.add(2); instance.add(3); instance.add(4.0); instance.add(5.0); instance.add(6.0); instance.add(true); instance.add(false); instance.add('Pedro'); instance.add('Lemos'); print(instance.get<int>()); print(instance.get<double>()); print(instance.get<bool>()); print(instance.get<String>()); }
Результатом будет:
[1, 2, 3] // All int values [4.0, 5.0, 6.0] // All double values [true, false] // All bool values [Pedro, Lemos] // All String values
Хорошо, этот пример работает, но нереален, правда? Давай займемся чем-нибудь более полезным. А теперь представьте, что мы разрабатываем систему для управления заказами для пекарни. У нас есть такие классы:
class Item { final String type; final double price; Item({this.type, this.price}); } class Bread extends Item { Bread({String type, double price}) : super(price: price, type: type); } class Cake extends Item { Cake({String type, double price}) : super(price: price, type: type); } class Coffee extends Item { Coffee({String type, double price}) : super(price: price, type: type); }
Мы можем переименовать MyClass в BakeryOrder:
class BakeryOrder { var _order = <dynamic>[]; void addNewItem(dynamic item) => _order.add(item); List<T> getItemsOfType<T>() => _order.whereType<T>().toList(); }
Мы можем добавлять новые элементы в наши заказы с помощью метода addNewItem и получать список элементов определенного типа с помощью метода getItemsOfType:
void main() { final instance = BakeryOrder(); instance.addNewItem(Bread()); instance.addNewItem(Coffee()); instance.addNewItem(Cake()); print(instance.getItemsOfType<Bread>()); print(instance.getItemsOfType<Coffee>()); print(instance.getItemsOfType<Cake>()); }
Результатом будет:
[Instance of 'Bread'] [Instance of 'Coffee'] [Instance of 'Cake']
Вот и все! Вы можете использовать дженерики для достижения такого результата и многого другого. Надеюсь, вам понравилась эта статья!