Перейти до змісту

Множинне успадкування

Множинна спадко́вість — властивість деяких обʼєктно-орієнтованих мов програмування, в яких класи можуть успадкувати поведінку і властивості більш ніж від одного суперкласу (безпосереднього батьківського класу). Це відрізняється від простого спадкування, у випадку якого клас може мати тільки один суперклас.

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, домішки). Клас-міксін проектується так, щоб при створенні похідного класа він додавав би якісь нові властивості. Як правило екземпляри від міксінів не інстанціюють.

Back to top