假设我们有两个类 - Car
和Engine
。问题是从Car
继承Engine
以使用Engine
的方法(例如start()
)或使用Engine
作为属性更合理一个Car
?
第一种方法(显而易见):
class Car(Engine):
# do smth with inherited methods
第二种方法:
class Car():
def __init__(self, engine)
self.engine = engine
def start(self):
return self.engine.start()
class EngineV4():
def start(self):
pass
class EngineV8():
def start(self):
pass
# usage
engine = EngineV8()
car = Car(engine)
也是一个版本 - 继承Car
:
class V8Car(EngineV8, Car): pass
第一种方法在某些方面看起来不错,但会导致汽车拥有的所有东西的继承(例如Car(Engine, Lights, Wheels)
。
我想,这不是OOP继承的目的,更不用说灯光不是常识中的汽车来继承。
第二种方法允许汽车使用不同的发动机,例如。
第三种方法导致与第一种方法相同的问题,除了它在另一个抽象层次上。
那么,实现这些事情最合乎逻辑的方式是什么?我理解这个问题更多是关于OOP基础知识而不是Python和古玩在哪里可以阅读更多关于此类案例的内容。
答案 0 :(得分:5)
正如Khelwood在评论中提到的那样,问问自己:"汽车是一种引擎?"。
从语义上讲,继承描述了一个"是一个"关系 - 如果" B"继承自" A"然后" B"是一个" A" (cf liskov's substitution principle)。从这个角度来看,"汽车"它不是一个"引擎",它有一个引擎,所以你想要组合("有一个"关系)。
从技术上讲 - 特别是在像Python这样的动态类型语言中,你不必严格要求子类型 - 继承(我的意思是实现继承)也是一种受限制的静态形式的组合/委托,所以你可能会看到代码使用继承主要用于实现重用,但这仍然存在于设计POV中的争议((这可能在将TestCase套件中的常见内容分解出来时就可以了)但确实不适用于您的示例。
作为一般规则,只有在真正拥有"时才使用继承。关系,否则使用组合/委托。
编辑:我刚在你的问题中注意到这一点:
class V8Car(EngineV8, Car):
pass
这正是您要在语义和技术上避免的。您将通过此设计获得的一些问题:
1 /汽车的发动机是可更换的部件,因此您应该能够在不更换汽车本身的情况下进行更换。当您的客户要求您添加这个"小功能"时,您将如何处理此问题?那是#34;如此明显"它不是初始要求的一部分吗?2 /每个汽车/发动机组合可能需要一个子类,每次添加新引擎类型时都需要创建一个新的汽车子类。祝你好运...现在添加轮子和一些其他功能,并找出你最终会有多少课程。
3 /如果你向Engine添加了一个新的属性/方法,它会影响现有的Car属性,那么你所有的客户端代码都会破坏 - 如果你很幸运的话,这会很明显,但往往是以更微妙的方式这可能不是系统的,也不是立即检测到的。然后,一旦你发现有什么东西破坏了,就必须编辑所有受影响的类,以确保在Car
而不是Engine
上正确解析该属性,并记住将此修复程序移植到所有新车上/引擎组合也是如此。在幸运的情况下,没有其他含义 - 你可能有(好吧,你可能拥有)Engine中的一些方法,它使用这个属性进行一些计算,但是然后这些方法将开始使用Car的属性,然后你将如何解决这个问题?
上述一些观点可能看起来像草书,但我已经看到IRL发生这样的事情并修复它们真是一团糟 - 这对公司来说是一个巨大的净亏损。
现在将制作引擎作为Car的一个组成部分与你所获得的结果进行比较:
class Engine():
# ...
class Car():
def __init__(self, engine):
self.engine = engine
car = Car(engine=Engine())
1 /汽车发动机是可更换的部件,因此您应该可以在不更换汽车的情况下进行更换
# Yeah we got this even better engine!
car.engine = V8SuperEngine()
2 /每个汽车/发动机组合可能需要一个子类,每次添加新引擎类型时都需要创建一个新的汽车子类
# don't need a new class...
newcar = Car(engine=V8SuperEngine())
3 /如果您向Engine添加了一个新的属性/方法,该属性/方法恰好隐藏了现有的Car属性,那么所有客户端代码都会中断
# Well, now this just cannot happen, period. Something else ?
答案 1 :(得分:2)
聚合是这项任务更有用的方法,因为它允许动态构造混凝土汽车,如下所示:
class Car():
def __init__(self, engine, wheels):
self.engine = engine
self.wheels = wheels
...
some_car = Car(concrete_engine, concrete_wheels)
使用聚合对象(更高的内聚力),就像这个方法一样:
def speed(self):
if self.wheels.is_locked():
return 0
return self.engine.swiftness() * self.factor