Вітання! Сьогодні ми докладно розглянемо ще один принцип об'єктно-орієнтованого програмування (ООП) – успадкування. Заодно вивчимо інші типи відносин між класами - композицію та агрегування. Ця тема не буде складною: ти вже багато разів стикався з наслідуванням та його прикладами у минулих лекціях. Сьогодні головним буде закріпити твої знання, детальніше розглянути механізм успадкування та ще раз пробігтися за прикладами:) Отже, поїхали!
Спадкування в Java та його переваги
Як ти, напевно, пам'ятаєш, спадкування (inheritance) — механізм, який дозволяє описати новий клас на основі існуючого (батьківського). При цьому властивості та функціональність батьківського класу запозичуються новим класом. Давай згадаємо приклад успадкування з попередніх лекцій:public class Car {
private String model;
private int maxSpeed;
private int yearOfManufacture;
public Car(String model, int maxSpeed, int yearOfManufacture) {
this.model = model;
this.maxSpeed = maxSpeed;
this.yearOfManufacture = yearOfManufacture;
}
public void gas() {
//...газ
}
public void brake() {
//...гальмо
}
}
public class Truck extends Car {
public Truck(String model, int maxSpeed, int yearOfManufacture) {
super(model, maxSpeed, yearOfManufacture);
}
}
public class Sedan extends Car {
public Sedan(String model, int maxSpeed, int yearOfManufacture) {
super(model, maxSpeed, yearOfManufacture);
}
}
Є програма, в рамках якої ми працюємо з різними типами автомобілів. Навіть якщо ти не автолюбитель, напевно знаєш, що типів цих самих автомобілів у світі безліч :) Тому загальні властивості автомобілів виділяємо в загальний клас-батьок - Car
. А що спільного у всіх автомобілів, незалежно від типу? Будь-яка машина має рік випуску, назву моделі та максимальну швидкість. Ці властивості виносимо у поля model
, maxSpeed
, yearOfManufacture
. Що стосується поведінки, будь-яка машина може газувати та гальмувати :) Цю поведінку ми визначаємо в методах gas()
і brake()
. Які вигоди нам це дає? Насамперед — скорочення обсягу коду. Звичайно, можемо обійтися без батьківського класу. Але оскільки кожна машина повинна вміти газувати та гальмувати, нам доведеться створювати методи.gas()
й brake()
у класі Truck
, у класі Sedan
, у класі F1Car
, класі Sportcar
й у всіх інших класах машин. Уяви, скільки зайвого коду ми при цьому напишемо. Не забувай і про поля model, maxSpeed і yearOfManufacture: якщо відмовимося від батьківського класу, будемо створювати їх у кожному з класів-машин! Коли у нас набереться пара десятків класів-машин, обсяг коду, що повторюється, стане дійсно серйозним. Винесення загальних полів та методів (ще кажуть — «стану» та «поведінки») до класу-батька дозволить нам заощадити купу часу та місця. Якщо ж якийсь тип має властивості або методи, унікальні тільки для нього і відсутні в інших типів машин, — не біда. Їх завжди можна створити в класі-нащадку, окремо від решти.
public class F1Car extends Car {
public void pitStop() {
//...піт-стоп роблять тільки гоночні автомобілі
}
public static void main(String[] args) {
F1Car formula1Car = new F1Car();
formula1Car.gas();
formula1Car.pitStop();
formula1Car.brake();
}
}
Візьмемо випадок із гоночними машинами Формули-1. Вони, на відміну «родичів», є унікальне поведінка — іноді вони заїжджають на піт-стоп. Нам це не заважає. Загальну поведінку ми вже описали в батьківському класі Car
, а специфічну поведінку класів-нащадків можемо додати усередині класів. Це стосується і полів: якщо у дочірнього класу є унікальні властивості, спокійно оголошуємо ці поля всередині нього і не переживаємо :) Можливість повторного використання коду – головна перевага спадкування. Для програміста дуже важливо не писати зайвого обсягу коду. Ти не раз зіткнешся з цим у роботі. Будь ласка, запам'ятай ще одну вкрай важливу річ: Java не має множинного успадкування.Кожен клас успадковується лише від одного класу. Про причини цього докладніше поговоримо у майбутніх лекціях, поки просто запам'ятай. Цим Java, до речі, відрізняється від інших ООП-мов. Наприклад, у С++ множинне успадкування є. З успадкуванням все більш-менш зрозуміло — йдемо далі.
Композиція та агрегування
Класи та об'єкти можуть бути пов'язані один з одним. Спадкування описує зв'язок «є» (або англійською «IS A»). Лев є Тваринним. Таке ставлення легко висловити за допомогою успадкування, деAnimal
буде батьківським класом, а Lion
нащадком. Однак не всі зв'язки в світі описуються таким чином. Наприклад, клавіатура безумовно якось пов'язана з комп'ютером, але вона не є комп'ютером . Руки якось пов'язані з людиною, але вони не є людиною. У цих випадках в його основі лежить інший тип відношення: не є, а є частиною (HAS A). Рука не є людиною, але є частиною людини. Клавіатура не є комп'ютером, але є частиною комп'ютера. Відносини HAS A можна описати в коді, використовуючи механізми композиції таагрегування . Різниця між ними полягає у «суворості» цих зв'язків. Наведемо простий приклад: У нас є наша Car
машина. Кожна машина має двигун. Крім того, кожна машина має пасажирів усередині. У чому принципова різниця між полями Engine engine
і Passenger [] passengers
? Якщо у машини всередині сидить пасажир А
, це не означає, що в ній не можуть перебувати B
пасажири C
. Одна машина може відповідати кільком пасажирам. Крім того, якщо всіх пасажирів висадити із машини, вона продовжить спокійно функціонувати. Зв'язок між класом Car
та масивом пасажирів Passenger [] passengers
менш суворий. Вона називається агрегацією . Є непогана стаття з цієї теми: Відносини між класами (об'єктами). У ній наведено ще один добрий приклад агрегації. Припустимо, у нас є клас Student
, який позначає студента, та клас StudentsGroup
(група студентів). Студент може входити і до клубу любителів фізики, і до студентського фан-клубу «Зоряних війн» або команди КВК. Композиція - суворіший тип зв'язку. При використанні композиції об'єкт не тільки є частиною якогось об'єкта, але й не може належати до іншого об'єкта того ж типу. Найпростіший приклад – двигун автомобіля. Двигун є частиною автомобіля, але може бути частиною іншого автомобіля. Як бачиш, їх зв'язок набагато суворіший, ніж у Car
і Passengers
.