Мы рассмотрим Records, изучим их возможности, а также приведем примеры использования. Будь Вы начинающий или опытный разработчик Dart, эта статья даст вам понимание того, как записи могут помочь вам писать более качественный код.
Многие языки программирования имеют такой тип, как кортеж (tuple или product):
var tuple = (“a”, true);
Кортеж — это упорядоченный список позиционных полей.
Эти языки также скорее всего имеют тип Record
. Отличие записей от кортежей состоит в том, что поля в записи не упорядочены и являются именованными.
В Dart же записи могут иметь только позиционные параметры, либо только именованные, либо и то и другое.
Что такое Record в Dart?
Record
— это новый тип данных в Dart. Он предоставляет лаконичный синтаксис для объявления классов, которые являются простыми носителями постоянных, неизменяемых наборов данных. С помощью Record
вы можете легко создавать структуры данных, которые объединяют существующие данные. Например, чтобы вернуть пару значений:
(double lat, double long) myLocation(String name) {
// Логика получения локации
return (56.145748, 47.252178);
}
Enum
в Dart является особой формой класса, которая реализует определенный шаблон, но с минимальной синтаксической избыточностью — компилятор генерирует множество кода за нас.
Точно также, Record
в Dart является особой формой класса, который реализует шаблон носителя данных с минимальным синтаксисом. Весь избыточный код будет автоматически сгенерирован компилятором.
Record
является подтипом Object
и dynamic
и супертипом для Never
. Все записи являются подтипами Record
и супертипами Never
. Поля в Record
неизменяемы, но они могут содержать ссылки на изменяемые объекты. Он также предоставляет реализации hashCode()
, ==
и toString()
Подобно List
и Map
, Record
позволяет объединить несколько значений в один объект. В отличие от других типов коллекций, имеет фиксированный размер и гетерогенен. Каждый элемент в Record
может иметь свой тип.
Синтаксис
Синтаксис очень похож на список аргументов функции:
var record = (true, name: ‘Bill’, 2)
К полям записи можно получить доступ с помощью геттеров. Каждое именованное поле предоставляет геттер с таким же именем, а для позиционных полей предоставляются геттеры с именами $0
, $1
, $2
и т.д. Пример:
const city = (56.145748, 47.252178, name: 'Чебоксары');
print('lat: ${city.$0}'); // lat: 56.145748
print('long: ${city.$1}'); // long: 47.252178
print('city: ${city.name}'); // city: Чебоксары
Если некоторые ограничения при выборе имени для поля записи:
Не стоит использовать имя поля более одного раза.
Запись только с одним позиционным полем должна иметь завершающую запятую:
var string = ('I am String!');
var record = ('I am Record!',);
Имя поля не может начинаться с _
Поле не может иметь имя –
hashCode
,runtimeType
,noSuchMethod
илиtoString
Имя не должно конфликтовать с синтезированным именем геттера позиционного поля
В данном примере именованное поле $0
конфликтует с геттером первого позиционного поля.
var record = (0, $0: 1, 2);
Аннотация типов
В Dart каждая запись имеет соответствующий тип, который похож на список параметров функции:
(int, String name, bool) record;
Каждое поле представляет собой аннотацию типа и необязательное имя, которое будет полезно для документации.
Именованные поля помещаются в фигурные скобки, почти также как в функциях, но без required
и необязательных параметров. Просто тип поля и имя:
({int age, String name}) employee = (age: 40, name: 'Bill');
Также в аннотации записи можно использовать как позиционные, так и именованные поля:
(bool, num, {int n, String s}) pizza;
Вот как будет выглядеть объявление пустой записи без полей:
() emptyRecord;
Пример без Record
Здесь для объяснения записей мы будем использовать в качестве примера класс, содержащий данные о локации пользователя. Мы увидим, как c помощью Record
вы сможете сделать свой код менее подробным и более простым.
Давайте посмотрим, как может выглядеть класс Location
без использования записей:
class Location {
final double lat;
final double long;
Location(this.lat, this.long);
@override
String toString() {
return 'Location(lat: $lat, long: $long)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is Location &&
(identical(other.lat, lat) || other.lat == lat) &&
(identical(other.long, long) || other.long == long));
}
@override
int get hashCode => Object.hash(runtimeType, lat, long);
}
Далее объект Location
может быть создан следующим образом:
class LocationService {
Future<Location> getUserLocation() async {
// Логика получения координат
return Location(56.145748, 47.252178);
}
}
Пример с Record
Давайте посмотрим, как Record
изменит ситуацию:
var location = (lat: 56.145748, long: 47.252178);
Создав эту запись, мы заметно уменьшили количество кода и повысили удобочитаемость.
Верхняя и нижняя границы
Вычисления границ для типов записей включены в основную спецификацию здесь. Кратко:
Если 2 записи имеют одинаковую форму, то их наименьшей верхней границей является новый тип записи той же формы, где тип каждого поля является наименьшей верхней границей соответствующего поля в исходных типах:
(num, String) a = (1.2, "s");
(int, Object) b = (2, true);
var c = condition ? a : b; // c имеет тип `(num, Object)`.
Нижняя граница двух типов записей с одинаковой формой — это наибольшая нижняя граница составляющих их полей:
a((num, String) record) {}
b((int, Object) record) {}
var c = condition ? a : b; // c имеет тип `Function((int, String))`.
Наименьшей верхней границей двух типов записей с разными формами является Record
:
(num, String) a = (1.2, "s");
(num, String, bool) b = (2, "s", true);
var c = condition ? a : b; // c имеет тип `Record`.
Наибольшая нижняя граница записей различной формы равна Never
.
Резюме
Запись в Dart - это новый тип данных, обеспечивающий лаконичный синтаксис для объявления классов, которые действуют как простые носители постоянных, неизменяемых наборов данных. Их можно использовать в таких случаях, как множественные возвраты, объединение нескольких объектов, составные ключи, DTO и т.д. В целом, записи в Dart - это мощный и гибкий инструмент, который может упростить работу с данными в ваших приложениях.
Материал подготовил Денис Петров | AppFox.ru специально для Habr.com
Комментарии (8)
Zalexei
00.00.0000 00:00Делает ли это код более грязным? Не логичнее было бы сделать упрощённое созданиие классов типа class Location(int lat, int lon) наподобие Котлину?
Sayonji
00.00.0000 00:00Отличие в приведении типов. В переменную типа (num, String) можно присвоить тип (int, String), с классами так не сработает. Нужно было бы кучу классов добавлять: Tuple2<A, B>, Tuple3<A, B, C>, ..., вот эти записи по сути оно и есть, только с синтаксическим сахаром.
paveltyurikov
00.00.0000 00:00В Dart же записи могут иметь только позиционные параметры, либо только именованные, либо и то и другое
Это как вообще?
PackRuble
00.00.0000 00:00Ну таки вот:
(int, bool) onlyPos; (String name) onlyRec; (int, String name, bool) posAndRec;
Sayonji
Деструктиризация есть?
aaabramenko
Да Introducing Dart 3 alpha