Ste*_*ini 80 python oop inheritance
假设您有以下情况
#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);
}
Run Code Online (Sandbox Code Playgroud)
如您所见,makeSpeak是一个接受通用Animal对象的例程.在这种情况下,Animal非常类似于Java接口,因为它只包含一个纯虚方法.makeSpeak不知道它传递的Animal的性质.它只是发送信号"speak"并留下后期绑定来处理调用哪种方法: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)
Run Code Online (Sandbox Code Playgroud)
现在,在此示例中,您将看到相同的策略.您使用继承来利用Dogs和Cats都是动物的分层概念.但在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)
Run Code Online (Sandbox Code Playgroud)
在Python中,您可以将信号"发声"发送到您想要的任何对象.如果对象能够处理它,它将被执行,否则它将引发异常.假设您向两个代码添加了一个类飞机,并将一个Airplane对象提交给makeSpeak.在C++的情况下,它不会编译,因为Airplane不是Animal的派生类.在Python的情况下,它将在运行时引发异常,甚至可能是预期的行为.
另一方面,假设您使用方法speak()添加MouthOfTruth类.在C++的情况下,要么必须重构层次结构,要么必须定义不同的makeSpeak方法来接受MouthOfTruth对象,或者在java中,您可以将行为提取到CanSpeakIface并为每个对象实现接口.有很多解决方案......
我想指出的是,我还没有找到在Python中使用继承的一个原因(除了框架和异常树之外,但我想存在替代策略).您不需要实现基于派生的层次结构来执行多态.如果要使用继承来重用实现,可以通过包含和委派来完成相同的操作,还可以在运行时更改它,并清楚地定义包含的接口,而不会产生意外的副作用.
所以,最后,问题在于:Python中的继承点是什么?
编辑:感谢非常有趣的答案.实际上,您可以将它用于代码重用,但在重用实现时我总是很小心.一般来说,我倾向于做非常浅的继承树或根本没有树,如果一个功能很常见,我将它重构为一个通用模块例程,然后从每个对象调用它.我确实看到了有一个单一变化点的优势(例如,而不是添加到Dog,Cat,Moose等,我只是添加到Animal,这是继承的基本优势),但你可以实现相同的代表链(例如,JavaScript).我并没有声称它更好,只是另一种方式.
我也在这方面找到了类似的帖子.
Roe*_*ler 78
您将运行时鸭子类型称为"重写"继承,但我相信继承作为一种设计和实现方法有其自身的优点,是面向对象设计的一个组成部分.在我看来,你是否可以实现某些目标的问题并不是很相关,因为实际上你可以在没有类,函数等的情况下编写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"
Run Code Online (Sandbox Code Playgroud)
假设"speak_twice"是一个重要的功能,你不想在Dog和Cat中编写它,我相信你可以推断这个例子.当然,你可以实现一个Python独立函数,它将接受一些duck-typed对象,检查它是否有一个speak函数并调用它两次,但这都是非优雅的并且错过了第一个点(验证它是一个Animal).更糟糕的是,为了加强Encapsulation示例,如果后代类中的成员函数想要使用该"speak_twice"怎么办?
如果祖先类有一个数据成员,例如"number_of_legs"由祖先中的非抽象方法使用,则会更加清晰"print_number_of_legs",但是在后代类的构造函数中启动(例如,Dog会用4初始化它,而Snake会初始化它)用0).
同样,我确信有更多的例子,但基本上每一个(足够大)的软件都基于固体面向对象的设计将需要继承.
Jas*_*ker 10
Python中的继承比其他任何东西都更方便.我发现它最好用于为类提供"默认行为".
实际上,有一个重要的Python开发者社区反对使用继承.无论你做什么,不要只是不要过度.有一个过于复杂的类层次结构是一个肯定的方式被标记为"Java程序员",你就是不能拥有它.:-)
我认为Python中的继承点不是为了使代码编译,而是为了继承的真正原因,即将类扩展到另一个子类,并覆盖基类中的逻辑.然而,在Python中键入鸭子会使"接口"概念变得毫无用处,因为您可以在调用之前检查方法是否存在,而无需使用接口来限制类结构.
我认为用这些抽象的例子给出一个有意义的具体答案是非常困难的......
为简化起见,有两种类型的继承:接口和实现.如果你需要继承实现,那么python与静态类型的OO语言(如C++)没那么不同.
接口的继承是存在很大差异的地方,根据我的经验对软件设计产生根本性影响.像Python这样的语言并不强制你在这种情况下使用继承,在大多数情况下避免继承是一个好点,因为以后很难修复错误的设计选择.这是任何好的OOP书中提出的一个众所周知的观点.
有些情况下,在Python中建议使用接口继承,例如插件等...对于这些情况,Python 2.5及更低版本缺乏"内置"优雅方法,并且几个大框架设计了自己的解决方案(zope,trac,twister).Python 2.6及以上版本有ABC类来解决这个问题.
小智 5
在C++/Java/etc中,多态性是由继承引起的.放弃那种不正常的信念,以及动态语言向你敞开心扉.
从本质上讲,在Python中没有任何接口,只有"理解某些方法是可调用的".漂亮的手工波浪和学术声音,不是吗?这意味着因为你称之为"说话",你清楚地期望对象应该有一个"说话"的方法.简单,对吧?这是Liskov-ian,因为类的用户定义了它的界面,这是一个很好的设计概念,可以让你进入更健康的TDD.
所以剩下的就是,另一张海报礼貌地设法避免说,代码共享技巧.您可以将相同的行为写入每个"子"类,但这将是多余的.更容易继承或混合继承层次结构中不变的功能.较小的DRY-er代码通常更好.
鸭子打字毫无意义,这是接口 - 就像你在创建一个全抽象动物类时选择的接口一样.
如果你曾经使用过动物类来为其后代引入一些真实的行为,那么引入一些额外行为的狗和猫类就会有这两个类的原因.只有在祖先类没有为后代类提供实际代码的情况下,您的参数才是正确的.
因为Python可以直接知道任何对象的功能,并且因为这些功能在类定义之外是可变的,所以使用纯抽象接口"告诉"程序可以调用哪些方法的想法有点没有意义.但这不是唯一的,甚至是主要的继承点.