Припустимо, у вас така ситуація
#include <iostream>
class Animal {
public:
virtual void speak() = 0;
};
class Dog : public Animal {
void speak() { std::cout << "woff!" <<std::endl; }
};
class Cat : public Animal {
void speak() { std::cout << "meow!" <<std::endl; }
};
void makeSpeak(Animal &a) {
a.speak();
}
int main() {
Dog d;
Cat c;
makeSpeak(d);
makeSpeak(c);
}
Як бачите, makeSpeak - це процедура, яка приймає загальний об’єкт Animal. У цьому випадку Animal досить схожий на інтерфейс Java, оскільки містить лише чисто віртуальний метод. makeSpeak не знає природи Тварини, яку передає. Він просто надсилає йому сигнал «говорити» і залишає пізнє прив’язування, щоб подбати про те, який метод викликати: або Cat :: speak (), або Dog :: speak (). Це означає, що, що стосується makeSpeak, знання того, який підклас насправді переданий, не має значення.
Але як щодо Python? Давайте подивимося код того самого випадку в Python. Зверніть увагу, що я намагаюся на мить бути якомога подібнішим до випадку C ++:
class Animal(object):
def speak(self):
raise NotImplementedError()
class Dog(Animal):
def speak(self):
print "woff!"
class Cat(Animal):
def speak(self):
print "meow"
def makeSpeak(a):
a.speak()
d=Dog()
c=Cat()
makeSpeak(d)
makeSpeak(c)
Тепер у цьому прикладі ви бачите ту саму стратегію. Ви використовуєте спадщину, щоб використати ієрархічну концепцію як Собаки, так і Кішки як Тварини. Але в Python у цій ієрархії немає потреби. Це працює однаково добре
class Dog:
def speak(self):
print "woff!"
class Cat:
def speak(self):
print "meow"
def makeSpeak(a):
a.speak()
d=Dog()
c=Cat()
makeSpeak(d)
makeSpeak(c)
У Python ви можете надіслати сигнал "говорити" до будь-якого об'єкта, який хочете. Якщо об'єкт може мати з ним справу, він буде виконаний, інакше це спричинить виняток. Припустимо, ви додаєте клас Airplane до обох кодів і подаєте об’єкт Airplane до makeSpeak. У випадку C ++ він не компілюється, оскільки літак не є похідним класом тварин. У випадку з Python це спричинить виняток під час виконання, що може бути навіть очікуваною поведінкою.
З іншого боку, припустимо, ви додаєте клас MouthOfTruth із методом speak (). У випадку C ++ або вам доведеться реформувати свою ієрархію, або вам доведеться визначити інший метод makeSpeak для прийняття об'єктів MouthOfTruth, або в Java ви можете витягти поведінку в CanSpeakIface і реалізувати інтерфейс для кожного. Рішень багато ...
Я хотів би зазначити, що я ще не знайшов жодної причини використовувати успадкування в Python (крім фреймворків та дерев винятків, але я думаю, що існують альтернативні стратегії). вам не потрібно реалізовувати базову ієрархію для поліморфного виконання. Якщо ви хочете використовувати успадкування для повторного використання реалізації, ви можете зробити те саме за допомогою стримування та делегування, з додатковою перевагою, що ви можете змінити його під час виконання, і ви чітко визначите інтерфейс вміщеного, не ризикуючи небажаними побічними ефектами.
Отже, зрештою, стоїть питання: який сенс успадкування в Python?
Редагувати : дякую за дуже цікаві відповіді. Дійсно, ви можете використовувати його для повторного використання коду, але я завжди обережний при повторному використанні реалізації. Як правило, я схильний робити дуже дрібні дерева успадкування або взагалі не мати дерева, і якщо функціональність є загальною, я переробляю її як загальну процедуру модуля, а потім викликаю з кожного об'єкта. Я бачу перевагу однієї точки зміни (наприклад, замість того, щоб додавати до собаки, кота, лося тощо), я просто додаю до тварини, що є основною перевагою спадкування), але ви можете досягти цього за ланцюг делегування (наприклад, а-ля JavaScript). Однак я не стверджую, що це краще, просто інший спосіб.
Я також знайшов подібний пост з цього приводу.