我最近在处理一个正在处理的项目上的类继承问题,并且我开始对它作为编程概念感到困惑。我确实理解它的吸引力:它提供了一种干净的方法,可以使用新方法扩展现有的基类,从而避免多次重写相同的代码,并且避免了类之间如何相互关联的良好逻辑结构。
但是,由于我已经在更广泛地使用它,它的缺点变得更加明显。它不仅给方法或属性的来源增加了一层不透明,每次我想弄清楚给定方法的定义位置时,都迫使我陷入继承类的困境,而且还破坏了封装允许您在不经意间重新定义继承的类中的公共和私有函数以及变量。
这是一个非常简单的示例,说明用继承破坏事物有多么容易。
class Parent:
def __init__(self):
self._private_var = 10
def add_ten(self, n):
return n + self._private_var
class Child(Parent):
def __init__(self):
self._private_var = 100
def add_hundred(self, n):
return n + self._private_var
现在,假设我要使用Child
继承的.add_ten
方法:
c = Child()
c.add_ten(4)
>> 104
由于我在不知不觉中重新定义了Parent
的{{1}},所以._private_var
方法现在增加了100,而不是10。
在其他语言中,继承的处理方式可能稍有不同(我知道Python并没有任何真正的“私有”方法或变量,因此在Java或C ++中,这并不是什么大问题)。不过,在我看来,继承的弊端似乎超过了它的优势,因此我们希望尽可能避免完全使用它。
问题在于,替代方案似乎增加了很多冗余。
例如,我可以将.add_ten
定义为:
ChildTwo
这将使class ChildTwo:
def __init__(self):
self._parent = Parent()
self._private_var = 100
def add_ten(self, n):
return self._parent.add_ten(n)
def add_hundred(self, n):
return n + self._private_var
和.add_ten
的行为均符合预期,但是还需要我手动添加我想从.add_hundred
类继承的每个方法,这似乎在保持代码精简方面是浪费的。当我想从Parent
继承多种方法时,尤其如此。
我也不确定(?)是否为每个Parent
类实例化Parent
类是否会对性能产生影响。
在避免使用继承的同时仍尽可能避免代码重复并且对性能的影响最小的最佳方法是什么?
编辑:有人指出这是一个错误的示例,因为ChildTwo
应该定义为.add_ten
而不是n + 10
。这是一个公平的观点,但是它要求我知道n + self._private_var
的实现方式,但情况并非总是如此。如果Parent
在某个外部模块中,那么我无能为力。此外,如果将来Parent
的实现方式发生变化,它也会对.add_ten
类产生影响。
答案 0 :(得分:3)
对于何时以及何时不使用继承,显然没有硬性规定。但是,我需要做一些关键的事情来帮助避免问题。
我将子类视为父级逻辑的扩展。因此,我尝试避免覆盖对象,而只扩展它们。
例如,我通常有一个父类,该类接收项目的配置。然后,任何子类都可以使用这些配置并对其执行任何必要的逻辑。所有配置都是相同的,不会更改,因此继承不会造成任何问题。
class Parent:
def __init__(self, name, configs):
self.name = name
self.theory = configs['theory']
self.log_file = configs['log_file']
...
class Child(Parent):
def __init__(self, name, configs):
super().__init__(name, configs)
但是,我不会在父类中使用方法对配置执行某些操作,然后在子类中更改该方法。尽管这是完全可以接受的python代码,但我发现容易出错并且增加了不必要的复杂性。如果您要不断重写它,为什么还要麻烦写一个方法呢?
通过多重继承,如果您以前从未遇到过这种继承,那么使用“方法解析顺序”会很容易遇到问题。死亡钻石或其他具有戏剧性的名字。当多重继承导致子类在继承树中如何从其上级继承时产生歧义时,就会发生这种情况。因此,我完全避免让类成为“兄弟姐妹”。
继承通常很难扩展。我的意思是,在预先存在的继承结构中添加大量逻辑会引起问题。也许您的子类都以相同的方式使用了父类方法,但是现在您有了一个新的子类,该子类稍有不同。好的,所以您可以覆盖该方法。但是,如果您开始添加越来越多的子类也需要覆盖该方法,该怎么办?现在可以重写基类方法了,这意味着您需要重写所有被覆盖的方法。
有时候,继承将有助于减少重复,而有时,它将使维护,测试和扩展变得头痛。与编程一样,如果您发现自己一遍又一遍地写相同的东西,那说明您做错了。对我来说,确切地知道将来将使用什么类结构是确保任何继承不会引起问题的最佳方法。
我只想说您的榜样似乎有些草率。您建立了一个非常糟糕的结构,然后将继承视为失败的原因。如果要添加十个,请添加十个,不要添加一些可变变量。
最后,当我猛烈讨论个人喜好时,请注意在工作环境中,人们的喜好将与您的喜好大不相同。您应该了解如何使用,扩展和调试所有不同的类结构。