假设您有以下情况
#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中,您可以将“讲话”信号发送到所需的任何对象。如果对象能够处理它,则将执行该对象,否则将引发异常。假设您向两个代码都添加了一个飞机类,并提交了一个飞机对象进行makeSpeak。在C ++情况下,它将不会编译,因为Airplane不是Animal的派生类。在Python情况下,它将在运行时引发异常,甚至可能是预期的行为。
另一方面,假设您添加了一个带有say()方法的MouthOfTruth类。在C ++情况下,要么必须重构层次结构,要么必须定义另一个makeSpeak方法以接受MouthOfTruth对象,或者在Java中,您可以将行为提取到CanSpeakIface中并为每个实现接口。有很多解决方案...
我想指出的是,我还没有找到在Python中使用继承的单一原因(框架和异常树的一部分,但我想存在替代策略)。您无需实现基础派生的层次结构即可执行多态操作。如果要使用继承来重用实现,则可以通过包含和委派来完成此操作,其附加好处是可以在运行时更改它,并且可以清楚地定义所包含的接口,而不会冒意外副作用的风险。
因此,最后的问题是:Python继承的目的是什么?
编辑:感谢非常有趣的答案。确实可以将其用于代码重用,但是在重用实现时我总是要小心。通常,我倾向于做很浅的继承树,或者根本不做任何树,如果功能是通用的,我会将其重构为通用的模块例程,然后从每个对象中调用它。我确实看到了具有单个更改点的优点(例如,不添加到Dog,Cat,Moose等,而是添加到Animal,这是继承的基本优点),但是您可以通过以下方法实现相同的目的委托链(例如la JavaScript)。我并不是说这更好,只是另一种方式。
我在这方面也找到了类似的帖子。