OOP,Python - 类继承或属性赋值

时间:2018-03-22 12:02:49

标签: python oop

假设我们有两个类 - CarEngine。问题是从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和古玩在哪里可以阅读更多关于此类案例的内容。

2 个答案:

答案 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