Множинне успадкування
Множинна спадко́вість — властивість деяких обʼєктно-орієнтованих мов програмування, в яких класи можуть успадкувати поведінку і властивості більш ніж від одного суперкласу (безпосереднього батьківського класу). Це відрізняється від простого спадкування, у випадку якого клас може мати тільки один суперклас.
Python підтримує множинну спадковість:
>>> class Horse:
... def run(self):
... print("Я біжу!")
...
>>> class Eagle:
... def fly(self):
... print("Я лечу!")
...
>>> class Pegasus(Horse, Eagle):
... pass
...
>>>
У вищенаведеному прикладі клас Pegasus
:
- від класа
Horse
успадкував методrun()
- від класа
Eagle
успадкував методfly()
Перевіримо:
>>> p = Pegasus()
>>> p.run()
Я біжу!
>>> p.fly()
Я лечу!
>>>
Лінеарізація і множинна спадковість
При множинній спадковості виникає питання: а як саме треба виконувати лінеарізацію класів? Атже варіантів обійти всю ієрархію класів коли у одного класа є більше ніж один базовий клас можна побудувати декілька.
Самий простий приклад де виникає неоднозначність пошуку методів — задача ромбовидного успадкування:
>>> class Horse:
... def say_hello(self):
... print("Я - кінь!")
...
>>> class Eagle:
... def say_hello(self):
... print("Я - орел!")
...
>>> class Pegasus(Horse, Eagle):
... pass
...
>>> p = Pegasus()
>>> p.say_hello()
Виникає питання: якого саме класа буде викликано метод say_hello()
?
Підходів побудови лінеаризації при множинній спадковості існує декілька, в різних мовах програмування можуть використовуватись різні підходи. А в декотрих мовах програмування, які підтримують парадигму ООП, множинне успадкування відсутнє взагалі.
В Python використовується алгоритм C3-лінеаризації, котрий дозволяє побудувати стійкий список з самого класа і усіх його предків (батьків і прабатьків). Цей алгоритм вирішує більшість проблем при лінеаризації множинного успадкування.
Алгоритм відносно складний, зовсім спрощено його можна представити так:
- у список додається клас об'єкта (далі — дочірній клас)
- у список додаються базові класи дочірнього класа у тому порядку, як вони вказані при декларації дочірнього класа
- у список додаються базові класи базових класів і так далі рекурсивно аж до класа
object
- якщо якийсь клас опиняється у списку двічі — залишається тільки останнє його входження
Як результат, ми рухаємось по рівням, не звертаємось до базового класа до того, як звернемось до усіх його нащадків, навіть якщо нащадків у цього базового класа декілька.
Алгоритм забезпечує пошук перевизначеного метода базового класа, якщо цей метод перевизначено хоча б у одному нащадку цього базового класа.
>>> for cls in Pegasus.mro():
... print(cls.__name__)
...
Pegasus
Horse
Eagle
object
>>>
Як видно з прикладу, лінеарізація обходить усі класи нашої ієрархії.
Класи Eagle
і Horse
йдуть у тій послідовності,
як перераховані при визначенні класа Pegasus
.
Давайте поміняємо їх місцями і подивимось як це вплине на лінеарізацію:
>>> class Pegasus(Eagle, Horse):
... pass
...
>>> p = Pegasus()
>>> for cls in Pegasus.mro():
... print(cls.__name__)
...
Pegasus
Eagle
Horse
object
>>> p.say_hello()
Я - орел!
>>>
Практики використання
У множинної спадковості є як переваги, так і недоліки. До переваг можна віднести те, що множинна спадковість дозволяє проектувати доволі складні і гнучкі ієрархії класів. Але разом з тим використання цього механізму може призвести до появи у коді доволі серйозних помилок. Сама наявність множинної спадковості може бути індикатором наявності помилок в проектуванні архітектури вашого застосунку, тому що множинна спадковість на практиці використовується вкрай рідко. І якщо у вас виникає думка використати множинну спадковість, то треба добре подумати, чи правильно ви розбиваєте предметну область на класи взагалі. І тільки якщо ви дійдете до висновку, що використання множинної спадковості доречне і дійсно може спростити код, тоді вже можете використовувати її.
Є один патерн програмування, де множинна спадковість використовується. Це так звані "міксіни" (mixins, домішки). Клас-міксін проектується так, щоб при створенні похідного класа він додавав би якісь нові властивості. Як правило екземпляри від міксінів не інстанціюють.