假设你有以下情况
#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 接口(interface)非常相似,因为它只包含一个纯虚方法。 makeSpeak 不知道它通过的 Animal 的性质。它只是向它发送信号“speak”,并让后期绑定(bind)处理调用哪个方法: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)
现在,在此示例中,您会看到相同的策略。您使用继承来利用 Dogs 和 Cats 都是 Animals 的分层概念。 但在 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++ 的情况下,它不会编译,因为 Airplane 不是 Animal 的派生类。在 Python 的情况下,它会在运行时引发异常,这甚至可能是预期的行为。
另一方面,假设您添加了一个带有方法 speak() 的 MouthOfTruth 类。在 C++ 案例中,您必须重构层次结构,或者必须定义不同的 makeSpeak 方法来接受 MouthOfTruth 对象,或者在 java 中,您可以将行为提取到 CanSpeakIface 并为每个对象实现接口(interface)。有很多解决方案...
我想指出的是,我还没有找到在 Python 中使用继承的单一理由(除了框架和异常树,但我猜还有替代策略)。您无需实现基派生层次结构即可以多态方式执行。如果您想使用继承来重用实现,您可以通过包含和委托(delegate)来完成相同的操作,另外还有一个好处是您可以在运行时更改它,并且您可以清楚地定义包含的接口(interface),而不会冒意外副作用的风险。
那么,最后的问题是:在 Python 中继承有什么意义?
编辑:感谢您提供非常有趣的答案。事实上,你可以将它用于代码重用,但我在重用实现时总是很小心。一般来说,我倾向于做非常浅的继承树或根本不做树,如果一个功能很常见,我将它重构为一个公共(public)模块例程,然后从每个对象中调用它。我确实看到了单点更改的好处(例如,我只添加到 Animal,而不是添加到 Dog、Cat、Moose 等,这是继承的基本优势),但是您可以通过一个委托(delegate)链(例如,一个 JavaScript)。我并不是说它更好,只是另一种方式。
我还找到了 a similar post在这方面。
最佳答案
您将运行时鸭子类型称为“覆盖”继承,但是我相信继承作为一种设计和实现方法有其自身的优点,它是面向对象设计的一个组成部分。在我看来,你是否能以其他方式实现某些东西的问题并不是很相关,因为实际上你可以在没有类、函数等的情况下编写 Python 代码,但问题是你的代码的设计、健壮和可读性如何。
我可以举两个例子来说明我认为继承是正确的方法,我相信还有更多。
首先,如果您的编码明智,您的 makeSpeak 函数可能想要验证它的输入确实是一个 Animal,而不仅仅是“它会说话”,在这种情况下,最优雅的方法是使用继承。同样,您可以通过其他方式来实现,但这就是带有继承的面向对象设计的美妙之处——您的代码将“真正”检查输入是否是“动物”。
其次,显然更直接的是封装——面向对象设计的另一个组成部分。当祖先具有数据成员和/或非抽象方法时,这变得相关。举一个愚蠢的例子,其中祖先有一个函数(speak_twice),它调用了一个 then-abstract 函数:
class Animal(object):
def speak(self):
raise NotImplementedError()
def speak_twice(self):
self.speak()
self.speak()
class Dog(Animal):
def speak(self):
print "woff!"
class Cat(Animal):
def speak(self):
print "meow"
假设 "speak_twice"
是一个重要功能,您不想在 Dog 和 Cat 中都对其进行编码,我相信您可以推断这个示例。当然,您可以实现一个 Python 独立函数,该函数将接受一些鸭子类型的对象,检查它是否有一个 speak 函数并调用它两次,但这既不优雅,也错过了第 1 点(验证它是一个动物)。更糟糕的是,为了加强封装示例,如果后代类中的成员函数要使用 "speak_twice"
怎么办?
如果祖先类有一个数据成员,例如 "number_of_legs"
被祖先中的非抽象方法使用,例如 "print_number_of_legs"
,那就更清楚了, 但在后代类的构造函数中启动(例如,Dog 会用 4 初始化它,而 Snake 会用 0 初始化它)。
同样,我相信还有无穷无尽的示例,但基本上每个(足够大的)基于可靠的面向对象设计的软件都需要继承。
关于python - Python中的继承有什么意义?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1020453/