Extension в Dart (Flutter)

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



Введение


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

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

Проблема


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

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

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

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

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

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

Подробнее
Реклама

Комментарии 11

    +1
    Добавлю, что extension методы отлично подходят для придания поведения интерфейсам (с коей целью и были добавлены в C# вместе с LINQ).
      0
      и как поднять flutter до dart 2.6?
        0
        К сожалению сейчас вы не можете сделать разные проекты с разной версией Dart и вам придётся переключаться (возможно и нет, всё зависит от проекта). Насколько я знаю сейчас команда dart ведёт над этой фичёй разработку. Думаю в ближайшем будущем вы можете указать к каждому проекту определённую версию dart.

        на Mac:
        brew upgrade dart

        на Windows:
        choco upgrade dart-sdk

        на Linux:
        используйте apt-get
          0
          Прямо сейчас две команды, поднимет Dart до версии 2.7, но в проектах могут полезть косяки, тогда просто обратно на stable

          flutter channel beta
          flutter upgrade

          flutter.dev/docs/development/tools/sdk/upgrading#switching-flutter-channels
            0
            К сожалению я не могу перевести продакшен приложение на beta канал. Это может вызвать много проблем
          0
          Спасибо за статью! Но я немного не понял:

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

          Как здесь система поняла, что нужно взять Extension. Или мы просто декорируем Future на глобальном уровне и система видит берет onError из Extension?

          Если да, то Extension применяется, но глобальном уровне? Или делается import extension_future.dart ?
            0
            Для того чтобы вы могли использовать extension, вам нужно сделать import. Глобальных extension к сожалению нет.
            0
            Спасибо за перевод. Обычно принято указывать первоисточник.
            medium.com/dartlang/extension-methods-2d466cd8b308
              0
              Спасибо что обратили внимание, но к сожалению это не перевод, а личный опыт. Я не скрываю что примерами реализации я вдохновлялся в данной статье, так как по поему мнению это очень хороший пример
                0
                Вы не написали ссылку на статью, что оттуда весь код.
                Я до вашей статьи читал оригинал и у вас прямо слово в слово переведена ее большая часть. Просто вы не всю статью перевели :)
                Не знаю есть ли какие-то стандарты, например, 90% текста переведено + весь код взят из оригинала, а добавлена пара общих абзацев — это уже не перевод а своя статья?
                Креатив в добавлении общих фраз?
                Игра найди отличия









                  +1
                  Я тоже читал статью по ссылке, и соглашусь с awaik, что у вас именно сокращённый перевод статьи.

                  В тексте ошибка — «с лево направо» правильно пишется как «слева направо».

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое