В преддверии старта курса «Kotlin Backend Developer» приглашаем будущих студентов и всех желающих посмотреть открытый урок на тему «Пересмотр «12 факторов»: создаём современный микросервис на Kotlin».
Также делимся с вами традиционным переводом полезного материала.
Терминология Kotlin: функции-расширения и свойства-расширения
Когда вы использовали какой-либо API-интерфейс, хотелось ли вам добавить в него новые функции или свойства?
Для решения этой задачи вы можете использовать наследование (создать новый класс на базе существующего) или функцию, которая получает в качестве входного параметра экземпляр класса. В языке программирования Java эта задача обычно решается с помощью класса Utils, но он не виден при использовании функции автозавершения кода, что затрудняет поиск и делает использование этого класса менее интуитивно понятным. Оба этих подхода можно использовать для решения нашей задачи, но ни один из них не дает понятный и хорошо читаемый код.
К счастью, на помощь приходит Kotlin с функциями-расширениями и свойствами-расширениями. Они позволяют добавлять в класс нужный функционал без необходимости использовать наследование или создавать функцию, принимающую экземпляр класса в качестве параметра. В Android Studio эти расширения видны при использовании функции автозавершения кода, в отличие от соответствующего аналога в языке Java. Расширения можно использовать в сторонних библиотеках, Android SDK или пользовательских классах.
Читайте дальше, если хотите узнать, как повысить читаемость вашего кода с помощью расширений!
Использование функций-расширений
Представим, что у вас есть класс Dog, описывающий собаку, у которой есть имя, порода и возраст.
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> data class Dog(val name: String, val breed: String, val age: Int)
Допустим, некий приют для животных хочет расширить класс Dog, чтобы в нем была функция, которая печатает информацию о собаке, если кто-то захочет забрать ее себе. Для этого мы реализуем функцию-расширение, которая объявляется как обычная функция, но с одной особенностью: перед именем функции добавляется имя расширяемого класса с точкой. В коде функции вы можете использовать служебное слово this для обращения к объекту-получателю, и у вас есть доступ ко всем членам класса-получателя в пределах функции.
Вы можете вызвать функцию printDogInformation() так же, как вы вызываете любую другую функцию в классе Dog.
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> fun main() { val dog = Dog("Jen", "Pomeranian", 13) dog.printDogInformation() }
Вызов функций-расширений из кода на языке Java
Функции-расширения не являются частью расширяемого класса, поэтому при попытке вызвать их из Java мы не найдем их среди методов расширяемого класса. Как мы увидим позже, расширения декомпилируются в статические методы файла, в котором вы их определили, и получают в качестве входного параметра экземпляр расширяемого класса. Вот как бы выглядел вызов функции-расширения printDogInformation() из Java:
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> DogExtensionKt.printDogInformation(dog);
Функции-расширения для типов, допускающих неопределенное значение
Расширения можно также использовать для работы с типами, допускающими неопределенное значение (nullable). Вместо того чтобы делать проверку на null перед вызовом функции-расширения, мы можем создать функцию-расширение для nullable-типа и реализовать проверку на null в коде этой функции. Вот так будет выглядеть функция printInformation(), использующая тип, допускающий неопределенное значение.
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> fun Dog?.printInformation() { if (this == null){ println("No dog found") return } println("Meet ${this.name} a ${this.age} year old ${this.breed}") }
Как видите, не нужно делать проверку на null перед вызовом функции printInformation().
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> fun main() { val dog : Dog? = null dog.printInformation() // prints "No dog found" }
Использование свойств-расширений
Представим, что нашему приюту для животных также нужно знать, подходит ли собака по возрасту для передачи в новую семью. Для этого мы реализуем свойство-расширение isReadyToAdopt, которое будет показывать, превышает ли возраст собаки 1 год.
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> val Dog.isReadyToAdopt: Boolean get() = this.age > 1
Вы можете обратиться к этому свойству-расширению так же, как вы обращаетесь к любому другому свойству в классе Dog.
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> fun main() { val dog1 = Dog("Jen", "Pomeranian", 13) if(dog1.isReadyToAdopt){ print("${dog1.name} is ready to be adopted") } }
Переопределение функций-расширений
Невозможно переопределить существующую функцию-член класса. Если определить функцию-расширение с такой же сигнатурой, что и у существующей функции-члена класса, то всегда будет вызываться функция-член, так как то, какая именно функция вызывается, зависит от объявленного статического типа переменной, а не от типа значения данной переменной во время выполнения кода. Например, нельзя расширить функцию toUppercase(), применяемую к строковому типу (String), но можно расширить функцию convertToUppercase().
Следствие такого поведения можно увидеть, если попытаться расширить библиотечный тип, которым вы не владеете, когда владелец библиотеки добавил в библиотеку метод с той же сигнатурой, что и у вашего расширения. В этом случае будет вызываться библиотечное расширение, а вы получите только информацию о том, что ваша функция-расширение стала неиспользуемым методом.
Внутреннее устройство расширений
Мы можем декомпилировать функцию printDogInformation() в Android Studio. Для этого нужно выбрать в меню пункт Tools/Kotlin/Show Kotlin Bytecode (Инструменты/Kotlin/Показать байт-код Kotlin) и нажать кнопку Decompile (Декомпиляция). В декомпилированном виде метод printDogInformation() будет выглядеть так:
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> public static final void printDogInformation(@NotNull Dog $this$printDogInformation) { Intrinsics.checkParameterIsNotNull($this$printDogInformation, "$this$printDogInformation"); String var1 = "Meet " + $this$printDogInformation.getName() + ", a " + $this$printDogInformation.getAge() + " year old " + $this$printDogInformation.getBreed(); boolean var2 = false; System.out.println(var1); }
В реальности функции-расширения являются обычными статическими функциями, которым в качестве входного параметра передается экземпляр класса-получателя. Они не имеют никакой другой связи с классами-получателями. Именно поэтому отсутствуют резервные поля — такие функции на самом деле не добавляют члены в класс.
Заключение
В целом расширения являются полезным инструментом, который следует использовать внимательно. Используя их, следуйте указанным ниже рекомендациям, и ваш код станет более понятным и читаемым.
Что следует помнить:
-
Расширения преобразуются в статические функции.
-
Функциям-членам класса всегда отдается предпочтение.
-
Возьмите собаку из приюта!
Успехов в программировании!
Узнать подробнее о курсе «Kotlin Backend Developer».
Посмотреть открытый урок на тему «Пересмотр «12 факторов»: создаём современный микросервис на Kotlin».

ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/533728/
Добавить комментарий