Хабрахабр

[Из песочницы] Extension в Dart (Flutter)

В недавном релизе языка Dart 2.6 в языке появилась новая функция, static extension или статические методы расширения, который позволяет вам добавить новые методы к существующим типам. Зачем вообще нужны extension? Как их использовать и на что они годятся?

Введение

Начнём с того что такое вообще extension? Extension — это синтаксический сахар, который расширяет существующий класс в месте, отличном от модуля объявления класса.

Extension активно используется в таких языках как C#, Java via Manifold, Swift, Kotlin и во множестве других.
В программировании методы расширения существуют уже достаточно давно, вот они добрались и до dart.

Проблема

Допустим у нас есть метод catchError, который просто ужасен и должен быть переписан на новую классную функцию. Допустим что он применяет в качестве аргумента функцию любого типа, в место строго типизированной функции или проверки типа функции, и это происходит потому что 8 месяцев назад при разработке данного функционала это было логично на тот момент.

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

подходит, по логическим соображениям, тогда я могу реализовать новую функцию Future, которая будет отвечать всем моим требованиям. Ну если первый вариант нам не.

abstract class Future<T> { ... /// Catches any [error] of type [E]. Future<T> onError<E>(FutureOr<T> handleError(E error, StackTrace stack)) => this.catchError(... тут делаю что-то очень умное...);
} ...
}

и буду её вызывать вот так:

Future<String> someString = ...;
someString.onError((FormatException e, s) => ...).then(...);

К сожалению я не могу добавить эту функцию в Future класс. Если я это сделаю, я также добавлю его в Future интерфейс, и любой другой класс, реализующий этот интерфейс, будет неполным и больше не будет компилироваться.

Ну ещё один из вариантов, это реализовать стороннию функцию которая будет выглядеть так:

Future<T> onFutureError<T, E>(Future<T> source, FutureOr<T> handleError(E error, StackTrace stack)) => source.catchError(...опять что-то умное...);

И её вызов будет выглядеть вот так:

Future<String> someString = ...;
onFutureError(someString, (FormatException e, s) => ...).then(...);

Супер, всё работает! Но печально что это стало ужасно читаться. Мы используем методы. которые реализованы внутри класса, так они вызываются -.doingSomething(); Данный код понятен, я его читаю просто с лево направо и простаиваю у себя в голове последовательность событий. Использование вспомогательной функции делает код громоздким и менее читаемым.

Ну тогда я могу реализовать новый класс и дам пользователям обернуть свой старый интерфейс с улучшенным функционалом.

class CustomFuture<T> { CustomFuture(Future<T> future) : _wrapper = future; Future<T> _wrapper; Future<T> onError<E>(FutureOr<T> handleError(E error, StackTrace stack)) => _wrapper.catchError(...что-то умнее чем в прошлый раз...);
}

и вызов будет выглядеть так:

Future<String> someString = ...;
CustomFuture(someString).onError((FormatException e, s) => ...).then(...);

Выглядит замечательно!

Решение проблемы при помощи extension

Как только мы перестанем программировать на pascal и вернёмся в 2019 год, реализация данного функционала сократиться до такого размера:

extension CustomFuture <T> on Future<T> { Future<T> onError<E>( FutureOr<T> handleError(E error, StackTrace stack)) => this.catchError(...something clever...);
}

и вот так будет выглядеть вызов:

Future<String> someString = ...;
someString.onError((FormatException e, s) => ...).then(...);

На этом всё! Решение данной проблемы заняло всего 5 строк кода. Вы. можете задаться вопросом, что за магия и как она работает?

Extension позволяет вам отпустить явное написание обёртки. На самом деле он ведёт себе так же как и класс-обёртка, хотя на самом дела это всего лишь вспомогательная статическая функция.

Это не wrapper

Дизайн расширения работает таким образом что он выглядит как объявление существующего класса, но действует также как если это был бы wrapper с приватным _wrapper. Но тут есть одно преимущество сравнению с wrapper классом, это обращение непосредственно к самому классу, а не обращаться к _wrapper класса-оболочке.

Это означает, что нет объекта-оболочки. Данный фича была сделан не ради фичи, а как я уже говорил ранее, расширения действительно являются более удобным способом вызова статических функций.

Это Все Статично

Я сказал “статические методы расширения” выше, и я сделал это не просто так!

Компилятор знает тип каждого выражения во время компиляции, поэтому, если вы пишете user.age(19), и age является расширением, то компилятор должен выяснить, какой тип обернут в данный объект, чтобы найти тип всего вызова. Дарт статически типизирован.

Какие проблемы могут возникнуть?

Самый простой пример проблем с extension, это когда у вас более одного расширения в его области видимости. В принципе, победителем является расширение наиболее близким к фактическому типу выражения, на которое вы вызываете член, с некоторыми оговорками.

Самый простой способ проблемы заключается в подключении строго того расширения которое вам необходимо, либо вы можете использовать явно использовать расширение:

...
List list = ...;
MyList(list).printlist();
SomeList(list).printlist();
... extension MyList on List
} extension SomeList on List { void printlist() { print(...что-то очень умное...); }
}

Итоги

  • В языке dart появился удобный инструмент для расширения существующего функционала.
  • Вы можете расширить методы, операторы, сеттеры и геттеры, но не поля.
  • Вы можете вызывать методы расширения явно или — когда нет конфликта с членом интерфейса или другим расширением-неявно.
  • Неявные вызовы работают так же, как и явные вызовы.
  • Расширения являются статическими. Все о них решается на основе статических типов.

Если вывод расширения не удается из-за конфликтующих расширений, то можно выполнить одно из следующих действий:

  1. Примените расширение явно.
  2. Импортируйте конфликтующее расширение с префиксом, потому что тогда оно недоступно для неявного вызова.
  3. Не импортируйте конфликтующее расширение вообще.

На этом всё! Можно использовать extension в полную силу.

Ну и конечно полезные ссылки:

Сайт flutter
Сайт Dart
Где можно почитать больше про extension
Телеграмм канал, где рассказываю про всё самое новое в мире Flutter и не только

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть