Extension в Dart (Flutter)

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

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

В программировании методы расширения существуют уже достаточно давно, вот они добрались и до dart. Extension активно используется в таких языках как C#, Java via Manifold, Gosu, JavaScript, Oxygene, Ruby, Smalltalk, Kotlin, Visual Basic.NET и Xojo.

Проблема
Допустим у нас есть метод 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 {
void printlist() {
print(…что-то умное…);
}
}

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

Итоги

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

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

  • Примените расширение явно.
  • Импортируйте конфликтующее расширение с префиксом, потому что тогда оно недоступно для неявного вызова.
  • Не импортируйте конфликтующее расширение вообще.
  • На этом всё! Можно использовать extension в полную силу.

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

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

    Оставить комментарий