Kotlin: Interfaces — все, що потрібно знати 🦄
Інтерфейс у мові програмування Kotlin — це абстрактний тип, який використовується для визначення поведінки, яку класи повинні реалізовувати. Інтерфейси схожі до протоколів. Інтерфейси оголошуються за допомогою ключового слова interface.
Інтерфейс не може бути інстанційованим, а тільки реалізованим (тобто не можна створити об’єкт інтерфейсу з використанням оператора new). Клас, що реалізує інтерфейс, повинен визначити усі методи, описані в цьому інтерфейсі, або має бути абстрактним класом.
Однією з переваг використання інтерфейсів є те, що вони імітують множинне успадкування. Усі класи Java повинні мати тільки один базовий клас, оскільки множинне успадкування класів не допускається. Однак, інтерфейс може успадковувати декілька інтерфейсів, так як і клас може реалізовувати декілька інтерфейсів.
Інше використання інтерфейсів — можливість використовувати об’єкт, фактично без наявності інформації про його тип, але про який відомо, що він реалізує певний інтерфейс. Виклик whistler.whistle() буде викликати реалізацію whistle метода об’єкта whistler незалежно від того, який конкретний тип має цей об’єкт, але тільки за умови, що він реалізує Whistler.
Interfaces in Kotlin
Інтерфейс — це спосіб надання опису або контракту для класів в об’єктно-орієнтованому програмуванні. Вони можуть містити властивості та функції абстрактним або конкретним способом залежно від мови програмування.
Ми детально розглянемо інтерфейси в Kotlin.
Інтерфейси в Kotlin схожі на інтерфейси в багатьох інших мовах, таких як Java. Але вони мають специфічний синтаксис, давайте розглянемо їх у наступних кількох підрозділах.
2.1. Defining Interfaces
Почнемо з визначення нашого першого інтерфейсу в Kotlin:
interface Car
Це найпростіший інтерфейс, який абсолютно порожній. Вони також відомі як маркерні інтерфейси. Інколи код пишеться таким чином, що завдяки порожньому інтенфейсу, ми можемо зрозуміти чи якийсь клас наслідується від нього чи ні. Це корисно, коли нам потрібно якийсь тригер для того, щоб щось зробити саме в “Х” випадку.
Тепер додамо деякі функції до нашого інтерфейсу:
interface Car {
fun isBroken(): Boolean
fun hasRatata(): Boolean {
return true
}
}
Ми додали два методи до нашого раніше визначеного інтерфейсу:
- Один із них під назвою isBroken є абстрактним методом
- У той час як інший під назвою hasRatata має реалізацію за замовчуванням.
в Kotlin всі змінні і методи за замовчування public, тому ми не пишемо це слово. Це — логічно.
Що стосується інтерфейсів, то в інтерфейсах всі методи — абстрактні. Слово abstract ми також не пишемо, що тоже — логічно.
Але пам’ятайте, що fun isBroken(), це публічна і абстрактна функція.
Давайте зараз додамо деякі властивості до нашого інтерфейсу:
interface Car {
val brand: String
val model: String
get() = “GT86”fun isBroken(): Boolean
fun hasRatata(): Boolean {
return false
}
}
Тут ми додали дві проперті до нашого інтерфейсу:
- Одна із них під назвою brand має тип String і є абстрактною
- Друга, яка називається model, також має тип string, але вона вже має реалізацію за замовчуванням.
Зверніть увагу, що проперті в інтерфейсах не можуть підтримувати стан.
Отже, наступне є неприпустимим виразом у Kotlin:
interface Сar {
val brand: String = “Toyota” // Illegal declaration
}
2.2. Implementing Interfaces
Тепер, коли ми визначили базовий інтерфейс, давайте подивимося, як ми можемо реалізувати це в класі в Kotlin:
class Toyota : Car {
override val brand: String
get() = this.javaClass.nameoverride fun isBroken(): Boolean {
return false
}
}
Зверніть увагу, що коли ми визначаємо Toyota як реалізацію Car, ми маємо забезпечити реалізацію лише для абстрактних властивостей і функцій. Однак ми також можемо перевизначити будь-яку раніше визначену властивість або функцію.
class Toyota : Car {
override val brand: String
get() = this.javaClass.nameoverride val model: String
get() = super.modeloverride fun isBroken(): Boolean {
return false
}
override fun hasRatata(): Boolean {
return super.hasRatata()
}
}
Inheriting Multiple Interfaces
Ми почнемо з визначення двох простих інтерфейсів:
interface Car {
fun isBroken(): Boolean
fun hasRatata(): Boolean
}interface SportCar {
fun wroomWroom(): Boolean
fun hasRatata(): Boolean
}
Для того щоб наслідуватись від декількох інтерфейсів нам потрібно просто перерахувати їх через кому, виглядає воно ось так:
class Toyota : Car, SportCar {
fun hasRatata(): Boolean // from Car
fun hasRatata(): Boolean // from SportCar
}
Зауважте, що обидва інтерфейси мають методи з однаковим контрактом.
Як ми бачимо, Toyota реалізує як Car, так і SportCar. Хоча синтаксично це досить просто, тут є трохи семантики, яка вимагає уваги. Ми розглянемо це в наступному підрозділі.
Resolving Conflicts
При реалізації кількох інтерфейсів клас може успадкувати функцію, яка має реалізацію за замовчуванням для того самого контракту в кількох інтерфейсах. Це викликає проблему виклику цієї функції з екземпляра класу реалізації.
Щоб вирішити цей конфлікт, Kotlin вимагає, щоб підклас надав перевизначену реалізацію для таких функцій, щоб зробити вирішення явним.
Наприклад, Toyota вище реалізує hasRatata(). Але, якби цього не було, Kotlin не знав би, чи потрібно викликати реалізацію за замовчуванням. З цієї причини Toyota повинен реалізувати anotherMethod.
Resolving the Diamond Problem
«Diamond problem» виникає, коли два дочірні об’єкти базового об’єкта описують певну поведінку, визначену базовим об’єктом. Тепер об’єкт, що успадковує обидва ці дочірні об’єкти, має вирішити, на яку успадковану поведінку він підписується.
Котлін вирішує цю проблему за допомогою правил, визначених для множинного успадкування. Давайте визначимо кілька інтерфейсів і клас реалізації, щоб представити проблему діаманта:
interface Car {
fun hasRatata(): Boolean
}interface ToyotaCar : Car {
override fun hasRatata(): Boolean {
return false
}
}interface ToyotaSportCar : SportCar {
override fun hasRatata(): Boolean {
return true
}
}
Тут ми визначили Car interface, який оголосив абстрактну функцію під назвою hasRatata. Інтерфейси ToyotaCar і ToyotaSportCar успадковуються від Car interface і реалізують функцію hasRatata по окремості. У кожного підінтерфейсу своя реалізація.
Тепер, коли всі реалізації розписані окремо, то ми можемо їх ЯВНО використати. Наприклад ось так.
class Toyota1 : ToyotaCar, ToyotaSportCar {
override fun hasRatata(): Boolean {
return super<ToyotaCar>.hasRatata()
}
}
Ми можемо імплементувати декілька інтерфейсів і навіть не зважаючи на це — ми повинні перевизначити функції цього інтерфейсу в класі, але на відміну від прямої реалізації ми можемо явно вказати, яка саме реалізація нас цікавить за допомогою super<SubInterface>.method().
🎬 Ось мій канал, де будуть всі відео на різні теми в програмуванні:
📚 Ось мій Patreon, де будуть всі матеріали в текстовому форматі, додаткова інфа, розбір приклдів, домашні завдання, ІТ-словник, і тд:
Окрім цього, там є різні пропозиції, наприклад, можемо займатись 1х1, можу допомогти зробити резюме, проведу співбесіду і так далі.
🎬 Весь контент, який я роблю — це все мій особистий досвід.
Я пишу/знімаю все сам і ділюсь з вами корисною інформацією, яку ніхто не розповідає, тому підтримайте підпискою ❤️
🙂 На цьому у мене — все!
👉🏻 Ставте лексуси, підписуйтесь і будьте умнічками.
Якщо я правий — похваліть, якщо ні — посваріть.
Но в любому випадку не забудьте дати фідбек 😉
Успіхів! 🇺🇦🦄