Экстеншн что это такое в программировании
Перейти к содержимому

Экстеншн что это такое в программировании

  • автор:

Экстеншн что это такое в программировании

Методы расширения (extension methods) позволяют добавлять новые методы в уже существующие типы без создания нового производного класса. Эта функциональность бывает особенно полезна, когда нам хочется добавить в некоторый тип новый метод, но сам тип (класс или структуру) мы изменить не можем, поскольку у нас нет доступа к исходному коду. Либо если мы не можем использовать стандартный механизм наследования, например, если классы определенны с модификатором sealed.

Например, нам надо добавить для типа string новый метод:

Для того, чтобы создать метод расширения, вначале надо создать статический класс, который и будет содержать этот метод. В данном случае это класс StringExtension . Затем объявляем статический метод. Суть нашего метода расширения — подсчет количества определенных символов в строке.

Собственно метод расширения — это обычный статический метод, который в качестве первого параметра всегда принимает такую конструкцию: this имя_типа название_параметра , то есть в нашем случае this string str . Так как наш метод будет относиться к типу string, то мы и используем данный тип.

Затем у всех строк мы можем вызвать данный метод:

Причем нам уже не надо указывать первый параметр. Значения для остальных параметров передаются в обычном порядке.

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

Также следует учитывать, что методы расширения действуют на уровне пространства имен. То есть, если добавить в проект другое пространство имен, то метод не будет применяться к строкам, и в этом случае надо будет подключить пространство имен метода через директиву using.

Extensions

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

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

Extension functions

Чтобы объявить функцию расширения, добавьте к ее имени префикс типа получателя , который относится к расширяемому типу. Следующее добавляет функцию swap в MutableList<Int> :

this ключевое слово внутри функции расширения соответствует объекту приемника (тот , который передается перед точкой). Теперь вы можете вызвать такую ​​функцию для любого MutableList<Int> :

Эта функция имеет смысл для любого MutableList<T> , и вы можете сделать ее универсальной:

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

Расширения разрешаются статически

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

Функции расширения отправляются статически , что означает, что они не являются виртуальными по типу получателя. Вызываемая функция расширения определяется типом выражения, для которого вызывается функция, а не типом результата оценки этого выражения во время выполнения. Например:

В этом примере выводится Shape , поскольку вызываемая функция расширения зависит только от объявленного типа параметра s , который является классом Shape .

Если у класса есть функция-член и определена функция расширения, которая имеет тот же тип получателя, то же имя и применима к заданным аргументам, член всегда выигрывает . Например:

Этот код выводит метод класса .

Однако вполне нормально,если функции расширения перегружают функции-члены,имеющие одинаковое имя,но разную сигнатуру:

Nullable receiver

Обратите внимание, что расширения могут быть определены с типом приемника, допускающим значение NULL. Эти расширения могут быть вызваны для объектной переменной, даже если ее значение равно нулю, и они могут проверить this == null внутри тела.

Таким образом, вы можете вызвать toString() в Kotlin без проверки на null , поскольку проверка происходит внутри функции расширения:

Extension properties

Kotlin поддерживает свойства расширения так же,как и функции:

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

Расширения объектов-компаньонов

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

https://amdy.su/wp-admin/options-general.php?page=ad-inserter.php#tab-8

Область применения расширений

В большинстве случаев вы определяете расширения на верхнем уровне,непосредственно под пакетами:

Чтобы использовать расширение вне его декларирующего пакета,импортируйте его на месте вызова:

См. Импорт для получения дополнительной информации.

Объявление расширений в качестве членов

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

В случае конфликта имен между членами получателя отправки и получателя расширения приоритет имеет получатель расширения. Чтобы обратиться к члену получателя отправки, вы можете использовать уточненный синтаксис this .

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

Примечание по видимости

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

Расширение, объявленное на верхнем уровне файла, имеет доступ к другим private верхнего уровня в том же файле.

Если расширение объявлено вне своего типа получателя, оно не может получить доступ к private или protected членам получателя .

Что такое функции расширения Kotlin и где их правильно применять?

Меня зовут Аксёнов Вячеслав, я профессиональный Java/Kotlin бэкенд разработчик в крупном энерпрайзе. В моей зоне ответственности — проектирование систем из набора сервисов и выстраивание сложной бизнес логики. Также время от времени я пишу свои пет проекты, некоторые из которых вы можете найти в моем github:

Уже больше года я использую преимущественно Kotlin для написания кода, и в этой статье хочу остановиться на одной из конструкций этого языка — функциях расширения. А также рассмотреть случаи, когда их использование может помочь, а когда сделает вашу жизнь только сложнее.

Чем являются функции расширения Kotlin на самом деле

По своей задумке, функция расширения Kotlin — это дополнительный метод для любого объекта, даже для потенциально несуществующего (нуллабельного). Этот инструмент является прямой реализацией переопределения методов паттерна проектирования декоратор. Благодаря удобному синтаксису пользоваться данной сахарной надстройкой в Kotlin очень просто.

Обычный dto класс Book, у которого есть некий набор полей. И есть класс SomeService, в котором есть публичный метод для анализа книги. Предполагаем, что для анализа книги требуется форматированные данные из dto. Однако класс закрыт для расширения, возможно он лежит в сторонней библиотеке. С помощью функции расширения можно написать метод, который будет словно бы принадлежать данному классу. При том обратите внимание, что на этот метод можно повесить любой модификатор доступа — в данном случае private.

Если скомпилировать данный код и разобрать байткод в Java, то конструкция с методом будет выглядеть следующим образом:

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

Расширение API класса, который вам не принадлежит

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

Конвертация моделей из одной в другую

Потенциально опасное использование, но если держать в голове следующие правила, то очень даже удобное.

Обратите внимание на нейминг функций. Следует их начинать с непопулярных названий методов, либо делать конвертеры приватными в области видимости только тех мест, где они нужны. Иначе можно замусорить весь контекст проекта. Взгляните на утрированный пример:

Иллюстрация: функции расширения Kotlin

Расширение возможностей любого класса, но в ограниченной области видимости

Представьте, что есть класс для которого нужно написать сложный метод, который будет использоваться только в одном сервисе. Это вполне можно сделать с помощью функции расширения Kotlin, главное — сделать расширение приватным.

В данном примере внутри функции расширения Kotlin используется поле сервиса — thirdPartyClient, из которого получаются некие мета данные. Это вполне допустимое использование функции расширения, но следует быть предельно осторожным если вы помещаете внутрь такого метода любое внешнее поле! Для этого должна быть веская причина!

Расширение функционала дженериков

Да, Kotln позволяет делать даже такое, и иногда это действительно полезно. К примеру вам может потребоваться расширить API всех наследников некоего класса, но без добавления метода в этот класс.

Будьте внимательны, если имя этого метода будет слишком общим, вы рискуете очень сильно засорить контекст IDE в местах использования этого метода.

Для чего никогда нельзя применять функции расширения

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

  • Первое. Бесконтрольное расширение любого функционала вместо написание обычного метода на глобальном уровне очень сильно засорит контекст IDE и станет большой болью в поиске всех мест, где был расширен какой-то класс.
  • Второе. Если размер функции расширения Kotlin превышает 5 строк, значит настало время писать обычный метод.

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

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

В заключение.

Функции расширения — удобный синтаксический сахар Kotlin, который позволяет использовать язык практически как угодно. Однако будьте бдительны и применяйте их только там, где они действительно упростят жизнь. Эти методы должны быть маленькими и решающими конкретную задачу, никто не намерен сделать cmd+click по методу и провалиться в ад с огромным количеством бизнес-логики.

Используйте их как вспомогательный инструмент и не злоупотребляйте их мощью ��

Урок 21. Extension (функции-расширения) языка Kotlin

Extensions функции расширения экстеншены - Android [Kotlin] для начинающих

Extension функция – это функция, которая не являясь участником какого-то класса (то есть не находясь внутри определенного класса), расширяет его функционал, имея доступ к публичным полям.

Чтобы лучше понять что это такое, лучше разобраться в вопросе “когда применяется extension функция?”

Разберем на примере класса File из стандартной библиотеки Java. Создадим экземпляр этого класса, в скобках указываем название будущего файла.

Провалимся в документацию. File – это класс. Конструктор принимает путь к файлу в систему — в нашем случае относительный путь. У него внутри есть поля и методы для работы с файлами. Например, метод createNewFile() — создает файл, если его не существует.

Но предположим, что нам нужен такой метод для записи текста в файл, который еще и отображает в консоль то, что записано в файл. В классе File этого метода нет, а также у вас нет доступа для редактирования этого файла. Он по умолчанию read only. Потому, что этот файл находится внутри JDK. Если нажать на “прицел”, увидим его расположение. Вот тут находятся все инструменты, что мы выбирали при создании проекта. И здесь в стандартной библиотеке лежит этот файл класса File в скомпилированном виде. Редактировать его нельзя.

Нам нужно записать текст в файл и выполнить какие-то дополнительные манипуляции. Поэтому создадим функцию-расширение для класса File, которая будет записывать текст в файл и выводить информацию в консоль. Дословно это будет звучать так: хочу, чтобы у файла (у класса File) был еще вот такой-то функционал. Extension-функция объявляется как и обычная с ключевого слова fun, однако, перед названием проставляется тип того класса для которого мы расширяем функционал. И пишем этот функционал внутри фигурных скобок созданной функции.

И, вызвав эту функцию у объекта класса File, к нему применится определенное действие. Причем можно не просто добавлять свой функционал, а можно еще внутри вызывать какие-то функции из класса, к которому применяем функцию-расширения. В данном случае createNewFile() и getName() (здесь она написана в сокращенном формате name, вместо функции геттера) это функции класса File. * writeText*() тоже применяется к объекту файла, но не напрямую. Об этом будет ниже.

Разработчики стандартной библиотеки Kotlin уже встроили функцию writeText() в стандартную библиотеку Kotlin. Провалимся в исходник и увидим такую же сигнатуру, которую мы написали для создания собственной extension функции. В Котлин уже есть собственная функция для записи текста в файл и она является частью языка. Как видите, функция НЕ является частью класса File. Она вообще не рядом с классом File. Однако, расширяет его функционал, за счет указания типа перед названием в сигнатуре функции.

То есть если бы мы писали этот функционал без Котлин, нужно было бы самостоятельно реализовывать весь функционал, описанный в этих функциях. Создавать FileOutputStream, записывать массивы байтов и так далее.

Но сейчас, например, чтобы записать текст в файл нам нужно только воспользоваться extension функцией, которую предоставляет API Kotlin. И применить ее к экземпляру класса File. Такие функции в среде разработки подсвечиваются желтым цветом.

Помимо функций есть также extension-свойства. Расширения на самом деле не добавляют никаких членов к классам и свойство-расширение не может иметь контейнеры field. Еще их называют Backing fields. Для свойств-расширений нельзя использовать инициализаторы. Их поведение может быть определено только явным образом, с указанием геттеров/сеттеров.

Приведем такой пример. Объявим свойство-расширение nameWithoutExtension. Его будем использовать чтобы получить имя файла без расширения.

Теперь, если вызвать это свойство у объекта файла – получим ожидаемую строку с названием файла, у которого “отсечено” расширение. Выведем код в консоль.

Вот в чем крутость Kotlin и крутость функций-расширения. Extension функции позволяют расширять функционал классов, к исходному коду которого у вас нет доступа. Но добавлю на будущее. Не стоит злоупотреблять такими функциями. Если внутри проекта есть какой-то класс, который требует расширения – просто добавьте в него требуемый метод. Во избежание ненужного разброса, так как ваш написанный класс будет в одном месте. А extension’ов вы насоздаете в другом, что может размазать логику по проекту.

Взаимодействие с файлами и extension’ами мы используем в рамках создания курсового проекта телеграм-бота на Kotlin. А также участникам практики доступны более продвинутые темы.

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

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