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

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

bj8seekf1mjnc7s2me-ydcgixks.png

Введение


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

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

Проблема


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

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

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

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


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

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


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

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

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


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

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


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

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

class CustomFuture {
  CustomFuture(Future future) : _wrapper = future;
  Future _wrapper;

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


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

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


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

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


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

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


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

Future 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 и не только

© Habrahabr.ru