Kotlin для початківців: Структури даних — Абстракції
Архів
22 листопада 2022 р.7 хв читанняОновлено 27 лютого 2026 р.Kotlin для початківців
Абстракції (Спадкування)
Завдання
Ускладнимо завдання: у нас є притулок з домашніми тваринами, і нам потрібно
зберігати уніфіковану інформацію про кожну тварину.
Якщо ви раніше створювали таблиці в тому ж Excel, ви, швидше за все, розумієте,
як ви можете виділити параметри кожного вихованця.
Що ж таке спадкування? Спадкування - це властивість об'єкта набувати
риси іншого об'єкта за допомогою абстракції.
Як же вирішуватимемо завдання? Давайте спочатку виділимо, які вихованці у нас
взагалі є:
- собаки
- коти
- папуги
Тепер нашим завданням є знайти загальні властивості даних об'єктів (вихованців)
щоб виділити їх у більш загальну сутність.
Перше, що швидше за все, спало на думку — це імена. У всіх домашніх
тварин є якісь імена. Відразу ж після цього, буде неважко додумати
деякі інші властивості: скільки їм років, їх опис (може, наприклад, це говорящий папуга або кіт)
та інші їх атрибути (властивості).
Також, кожен з них, має власні особливості, які існують тільки в них – собаки бігають,
папуги літають, а коти просто ліниві. Візуалізуймо:
Зі структурою розібрались, але як це реалізувати на Kotlin?
Interface
Одним з варіантів рішення є
interface. Цей тип опису структури припускає тільки опис контракту того, як
клас, що його наслідує (у відношені інтерфейсів ще часто говорять «імплеменує»), буде поводитись та які саме дані
буде мати.ℹ️ Термінологія
Контракт – формальний опис того, що робить будь-яка сутність (починаючи з функцій до класу або інтерфейсу).
Створюється інтерфейс наступним чином:
kotlin
Варто враховувати, що на відмінно від класів чи об'єктів — інтерфейси stateless (тобто, не можуть зберігати ніяких
даних). Також вони не є самостійною структурною одиницею й існують тільки за допомогою об'єктів, що їх реалізують
(імплементують, наслідують).
Тобто, ви не можете зробити наступне:
kotlin
Тому зробити можна тільки так:
kotlin
Спробуймо віднаслідувати даний інтерфейс:
kotlin
ℹ️ Інформація
Ключове словоoverrideвикористовується для того, щоб ініціалізовувати те, що не було ініціалізовано до цього або для того, щоб змінити те, що вже ініціалізовано, якщо можливо (розглянемо це питання нижче).
Для всіх ситуацій, окрім тих, де вам не потрібно (дійсно потрібно) зберігати якійсь дані у своїй абстракції
краще використовувати цей вид абстракцій. Але, розгляньмо й інший варіант того, як це можна зробити:
Abstract class
Іншим варіантом реалізації абстракції – є абстрактний класс. Він може все що й звичайний клас, але може мати
не ініціалізованих членів класу (функції або властивості) та не може бути зконструйований викликом конструктора. Може
бути тільки батьківським классом (тобто, реалізується через спадкоємця через спадкування).
Розгляньмо на прикладі.
kotlin
Ми не ініціалізували члени класу, який наслідуємо тому, при спробі запуску, отримаємо помилку:
text
💡 Цікаво знати
До речі, абстрактний клас може наслідувати абстрактний клас (і також інтерфейс може наслідувати інтерфейс).
Тому нам потрібно реалізувати наш клас:
kotlin
Але, що якщо ми хочемо зробити абстракцію можливою до використання без спадкоємця (наслідника)?
Open class
Цей вид класів може бути як віднаслідуваним, так і просто створеним:
kotlin
Цей клас може бути створеним:
kotlin
І також може мати спадкоємця:
kotlin
Начебто, все окей, але Kotlin нам скаже наступне:
'isEarthRound' in 'Foo' is final and cannot be overridden
Насправді таке ж би було, якщо ми б захотіли ініціалізувати в абстрактному класі не абстрактного члена.
Тому, за аналогією абстрактних членів, додамо до функції модифікатор
open.kotlin
Після чого ми вже зможемо переназначити (ініціалізувати) функцію:
kotlin
💡 Чому все так?
Для того, щоб дізнатись більше, чому всі члени в Kotlin за замовчуваннямfinal(фінальні, тобто їх вже не можна змінювати), можна прочитати про кризис базового класу.
Рішення
Але перейдім все ж таки до того, як ми розв'яжемо нашу задачу.
Насправді нам мало чим підходить
open class, бо в нас немає ніякого окремого 'Pet', а є конкретна тварина.
Абстрактний клас нам не підходить, бо в нас немає ніяких початкових значень та взагалі чогось, що було
б визначено початково (у нас все має визначати спадкоємець). Тому найкращим варіантом буде interface:kotlin
І віднаслідуємо:
kotlin
І створім функцію, що буде використовувати нашу абстракцію:
kotlin
І викличемо цю функцію:
kotlin
Для прикладу поки зробили так.
До речі, а як нам в подібній функції перевірити, яка саме реалізація була передана аргументом?
Наприклад, для того, щоб виконати унікальну дію нашого об'єкта (
fly() або run()).
Для цього існують два оператори:is: оператор, який говорить, чи є екземпляр вказаним об'єктом:kotlinas: оператор приведення типа до якогось іншого:kotlin
Рекомендую використовувати перший оператор завжди, коли ви не впевнені в тому, що параметр не є конкретно
переданим типом об'єкта.
Зробім же нашу функцію повноцінно:
kotlin
І у нас все готово, але, до речі, це можна спростити:
kotlin
Викличемо нашу функцію:
kotlin
Ось ми й зробили нашу абстракцію!
💡 Цікаво знати
До речі, будь-який об'єкт за замовчуванням спадкує класAny. Наприклад, за допомогою цього, у будь-якого об'єкта єtoString().Але ніякої магії в цій функції немає: кожен спадкоємець, якщо хоче матиtoString(), має його реалізувати:kotlinРаніше ми розглядали базові типи Kotlin, що вже за замовчуванням мають реалізацію цих функцій. Але, з нашими об'єктами нам знадобиться робити це вручну (але насправді рідко коли це потрібно) або використовувати інші типи класів, про які ми поговоримо згодом.
Kotlin для початківців