Предположим, что у вас есть следующая ситуация.
#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. Обратите внимание, что я стараюсь быть как можно более похожим на случай С++ на мгновение:
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. В случае С++ он не будет компилироваться, так как Airplane не является производным классом Animal. В случае Python он будет создавать исключение во время выполнения, что может даже быть ожидаемым поведением.
С другой стороны, предположим, что вы добавляете класс MouthOfTruth с помощью метода speak(). В случае C + либо вам придется реорганизовать вашу иерархию, либо вам придется определить другой метод makeSpeak для приема объектов MouthOfTruth, либо в java вы можете извлечь поведение в CanSpeakIface и реализовать интерфейс для каждого. Существует множество решений...
Как мне хотелось бы отметить, что я не нашел единственной причины использовать наследование в Python (кроме фреймворков и деревьев исключений, но я предполагаю, что существуют альтернативные стратегии). вам не нужно реализовывать базовую иерархию для выполнения полиморфной работы. Если вы хотите использовать наследование для повторного использования реализации, вы можете сделать то же самое путем сдерживания и делегирования, с дополнительным преимуществом, которое вы можете изменить во время выполнения, и вы четко определяете интерфейс содержащегося, не рискуя непреднамеренными побочными эффектами.
Итак, в конце концов, встает вопрос: какая точка наследования в Python?
Изменить: спасибо за очень интересные ответы. Действительно, вы можете использовать его для повторного использования кода, но я всегда осторожен при повторном использовании реализации. В общем, я, как правило, делаю очень неглубокие деревья наследования или вообще не дерево, и если функциональность является общей, я реорганизую ее как общую процедуру модуля, а затем вызываю ее из каждого объекта. Я вижу преимущество наличия одной единственной точки изменения (например, вместо добавления к Dog, Cat, Moose и т.д., Я просто добавляю к Animal, что является основным преимуществом наследования), но вы можете достичь того же с цепочка делегирования (например, a la JavaScript). Я не утверждаю, что это лучше, хотя, еще один способ.
Я также нашел подобное сообщение в этом отношении.