Наслідування — такий цікавий принцип, якого більшість сучасних програмістів взагалі намагається уникати. Що таке наслідування? За Вікіпедії це так: «абстрактний тип даних може наслідувати дані і функціональність деякого існуючого типу, сприяючи повторному використанню компонентів програмного забезпечення».
Перекладаю на людський: один клас може наслідувати інший клас, його поля і методи. Що значить наслідувати? Перевикористати. Після того, як клас оголошує себе наслідувачем якогось класу, відповідні поля і методи з’являються в ньому автоматично. Цей принцип використовується в різних мовах. В Java це extenсe, в С ++ це двокрапка, в Ruby – трикутна дужка, і так далі.
Що таке наслідування
Наслідування — це форма відносин між класами. Клас-наслідувач використовує методи класу-предка, але не навпаки. Наприклад, клас «собака» є наслідувачем класу «тварина», але «тварина» не буде наслідувати властивості класу «собака». Отже, наслідувач — це більш вузький клас порівняно з предком.
При цьому наслідування називається словом extenсe, що означає “розширення”. Наприклад, ми вказуємо для класу «собака» поле «лапи» – а для класу «тварина» ми не можемо його використовувати, тому що у тварин часто зовсім немає лап, якщо це риба чи змія. Так що клас-наслідувач може розширювати властивості базового класу, використовуючи його код.
Яке буває наслідування?
Наслідування буває одиночне і множинне. Одиночне — це коли один або кілька класів наслідуються тільки від одного базового класу. Множинне наслідування — коли клас наслідується від декількох базових класів. Множинне наслідування є в багатьох мовах: ви можете перенести дані і \ або поведінку з інших класів. Але, наприклад, в Java множинне наслідування обмежено і можливо тільки від певного типу класів, наприклад, від інтерфейсу — так, інтерфейс це теж клас.
Через що у багатьох мовах обмежують множинне наслідування? Через ромбовидного наслідування. Коли два класи наслідують властивості одного базового, а потім якийсь четвертий клас наслідує другий і третій одночасно. Виходить плутанина: незрозуміло, яка реалізація повинна використовуватися. У деяких мовах, наприклад, Scala, це вирішили за допомогою порядку записи. Але це не така вже й важлива проблема: зрештою, множинне наслідування не так вже необхідно, так що не таке велике це і обмеження.
Найважливіше
У період моєї юності було прийнято наслідувати все від усього і перевикористати код виключно через наслідування. В результаті програмісти загрузли в позамежному рівні дерев наслідування. Кожен програміст придумував собі базовий клас (або декілька), від яких наслідувалось все. Типовою була ситуація, коли у класу був п’ятнадцятий або двадцятий рівень наслідування. У цих класах могло взагалі не бути коду, а назви у них були просто наркоманські. Ця мода привела до того, що безліч провідних програмістів переключилася на делегування замість наслідування. Це коли клас не буде наслідувати, а викликає інший клас. І вони, звичайно, мали рацію, але в результаті маятник хитнувся в інший бік.
📢 Підпишись на наш Ютуб-канал! 💡Корисні відео для програмістів вже чекають на тебе!
🔍 Обери свій курс програмування! 🚀 Шлях до кар’єри програміста починається тут!
Зараз багато початківці і не дуже програмісти вважають, що наслідування не треба використовувати ніколи, а треба використовувати делегування. На жаль, але таким чином вони стріляють собі в ногу. Припустимо, ви пишете кадрову систему. У вас є об’єкт типу «інженер», об’єкт типу «бухгалтер», об’єкт типу «менеджер». Якщо вони не є наслідниками від класу «person”, а просто три окремих класи, то щоб підрахувати кількість співробітників компанії, вам потрібно перебрати всі три списки. А коли додасться новий вид співробітників, вам потрібно не забути змінити весь код, який підраховує співробітників, і додати в нього четвертий список. Якщо ж знадобиться підрахувати, наприклад, тільки тих співробітників, які знаходяться в офісі, ви з розуму зійдете. У вас п’ять видів співробітників, які між собою не пов’язані між собою. Їх обробка займе купу часу, код виростає в рази. Це нерозумно.
Я бачив, як програмісти відмовлялися робити наслідування там, де воно буквально напрошувалося. Мій особистий принцип, який я і вам раджу: використовуйте наслідування, коли ці об’єкти дійсно виникають одна з одної. Нерозумно наслідувати непов’язаний об’єкт просто для того, щоб наслідувати властивості: тут краще застосувати делегування. Але в очевидних випадках, відмовившись від наслідування, ви вистрілите собі в ногу і створите масу проблем на рівному місці.
Так, я розумію, що з багатьма фреймворками виникають питання, як правильно Меппен, як правильно працювати з деревами наслідування, що робити, і так далі. Але якщо ви знайомі з GoF-овськими патернами, згадайте: майже всі вони використовують наслідування і поліформізм. А справжній поліморфізм, як ви здогадуєтеся, без наслідування практично не працює. Тому, якщо ви повністю відмовляєтеся від наслідування, ви відмовляєтеся від всієї потужності ООП і відкочується в кам’яний вік, в процедурне програмування. Наслідування — це одна з головних сил ООП, і відмовлятися від неї нерозумно.