— Привіт, Аміго! Зараз буде одна тема, якою, я думаю, ти частенько користуватимешся. Це – успадкування.

Програмування, для необізнаних, не відрізняється від магії. Тому почну з цікавої аналогії…

Припустимо, що ти чарівник і хочеш створити літаючого коня. З одного боку, ти міг би спробувати начарувати пегаса. Але оскільки пегасів у природі немає, це буде дуже непросто. Доведеться багато робити самому. Значно простіше взяти коня і начарувати йому крила.

Успадкування. Переваги успадкування - 1

У програмуванні такий процес називається «успадкування». Припустимо, тобі треба написати дуже складний клас. Писати з нуля довго, потім ще довго все тестувати та шукати помилки. Навіщо йти найскладнішим шляхом? Краще пошукати – а чи немає такого класу?

Припустимо, ти знайшов клас, який своїми методами реалізує 80% потрібної функціональності. Ти можеш просто скопіювати код у свій клас. Але таке рішення має кілька мінусів:

1) Знайдений клас вже може бути скомпільований у байт-код, а доступу до його вихідного коду в тебе немає.

2) Вихідний код класу є, але ти працюєш у компанії, яку можуть засудити на пару мільярдів за використання навіть шести рядків чужого коду. А потім вона засудить тебе.

3) Непотрібне дублювання великого обсягу коду. Крім того, якщо автор чужого класу знайде у ньому помилку та виправить її, у тебе ця помилка залишиться.

Є рішення більш вишукане, і без необхідності отримувати легальний доступ до коду оригінального класу. У Java ти можеш просто оголосити той клас батьком твого класу. Це буде еквівалентно тому, що ти додав код того класу до свого коду. У твоєму класі з'являться всі дані та всі методи батьківського класу. Наприклад, можна робити так: успадковуємося від «коня», додаємо «крила» — отримуємо «пегаса»

Успадкування. Переваги успадкування - 2

— Дуже цікаво, продовжуй.

— Успадкування можна використовувати і для інших цілей. Наприклад, у тебе є десять класів, які дуже схожі, мають дані та методи, що збігаються. Ти можеш створити спеціальний базовий клас, винести ці дані (і методи, що з ними працюють) у цей базовий клас і оголосити ті десять класів його нащадками. Тобто вказати у кожному класі, що у нього є батьківський клас – даний базовий клас.

Так само як переваги абстракції розкриваються лише поряд з інкапсуляцією, так і переваги успадкування значно сильніші, якщо використовується поліморфізм. Але про нього я розповім потім. Зараз ми розглянемо кілька прикладів використання успадкування.

Припустимо, ми пишемо програму, яка грає у шахи з користувачем, тоді нам знадобляться класи для фігур. Які б ти запропонував класи, Аміго?

— Король, Ферзь, Слон, Кінь, Тура і Пішак.

— Чудово. Нічого не прогавив.

— А які б дані ти запропонував зберігати у цих класах?

— Координати x та y, а також її цінність (worth). Адже деякі фігури цінніші за інші.

— А у чому відмінності цих класів?

— Відмінності у тому, як вони ходять, фігури. У поведінці.

— Так. Ось як можна було б описати їх у вигляді класів

class King
{
int x;
int y;
int worth;
void kingMove()
{
//код, що вирішує,
//як ходитиме король
}
}
class Queen
{
int x;
int y;
int worth;
void queenMove()
{
//код, що вирішує,
//як ходитиме ферзь
}
}
class Rook
{
int x;
int y;
int worth;
void rookMove()
{
//код, що вирішує,
//як ходитиме тура
}
}
class Knight
{
int x;
int y;
int worth;
void knightMove()
{
//код, що вирішує,
//як ходитиме кінь
}
}
class Bishop
{
int x;
int y;
int worth;
void bishopMove()
{
//код, що вирішує,
//як ходитиме слон
}
}
class Pawn
{
int x;
int y;
int worth;
void pawnMove()
{
//код, що вирішує,
//як ходитиме пішак
}
}

— Так, саме так я б і написав.

— А ось як можна було б скоротити код за допомогою успадкування. Ми могли б винести однакові методи та дані до загального класу. Назвемо його ChessItem. Об'єкти класу ChessItem немає сенсу створювати, оскільки йому не відповідає жодна шахова фігура,проте від нього було би багато користі:

class King extends ChessItem
{
void kingMove()
{
//код, що вирішує,
//як ходитиме король
}
}
class Queen extends ChessItem
{
void queenMove()
{
//код, що вирішує,
//як ходитиме ферзь
}
}
class Rook extends ChessItem
{
void rookMove()
{
//код, що вирішує,
//як ходитиме тура
}
}
 class ChessItem
{
int x;
int y;
int worth;
}
 
class Knight extends ChessItem
{
void knightMove()
{
//код, що вирішує,
//як ходитиме кінь
}
}
class Bishop extends ChessItem
{
void bishopMove()
{
//код, що вирішує,
//як ходитиме слон
}
}
class Pawn extends ChessItem
{
void pawnMove()
{
//код, що вирішує,
//як ходитиме пішак
}
}

— Як цікаво.

— Саме так! Особливо багато переваг ми отримуємо, коли у проєкті тисячі різних об'єктів та сотні класів. Тоді правильно підібраними класами можна не лише суттєво спростити логіку, а й скоротити код у десятки разів.

— А що потрібно, щоб успадкувати якийсь клас?

— Для цього після оголошення нашого класу потрібно вказати ключове слово extends та написати ім'я батьківського класу. Успадкуватися можна лише від одного класу.

Успадкування. Переваги Успадкування - 3

На картинці ми бачимо «корову», успадковану від «свині». «Свиня» успадкована від «курки», «курка» від «яйця». Лише один батько! Таке успадкування не завжди є логічним. Але якщо є тільки свиня, а дуже потрібна корова, програміст часто не може встояти перед бажанням зробити «корову» зі «свині».

— А якщо мені хочеться успадкуватись від двох класів. Можна ж щось зробити?

— Майже нічого. Множинного успадкування класів у Java немає: клас може мати лише один батьківський клас. Але є множинне наслідування інтерфейсів. Це трохи знижує гостроту проблеми.

— Ясно. А що таке інтерфейс?

— Про інтерфейси я розповім тобі наступного разу, а поки що давай продовжимо розбиратися з успадкуванням.