覆盖方法 - 调用覆盖实现(超级)或不调用?

时间:2009-08-28 11:58:52

标签: oop inheritance

这个决定是否有一些经验法则?我总是坚持这个问题。即使我知道目前我不需要覆盖方法的结果,我怎么能确定将来不会修改overriden方法?例如,我正在扩展的父类的作者可能会决定在我覆盖的方法中实现一些副作用,并且没有副作用,对象的状态将是不正确的。< / p>

4 个答案:

答案 0 :(得分:3)

IMO,默认决策应始终是调用超类然后处理任何不同的行为。这是出于你提到的所有原因(现在可能需要,但如果没有,将来可能需要)。

唯一的例外是你提前知道超类会做你特别不想要的事情。但无论如何,这些问题都指向了设计问题。看到子类没有调用它们的父类是非常可疑的。

答案 1 :(得分:3)

在覆盖其中一个方法的基础上调用基类的实现有两个主要原因:

您希望扩展基类的行为。

在这种情况下,通常更容易依赖基类来完成工作的主要任务,并且只需在顶部添加额外的工作。这个决定通常很容易做出。

基本实现有一些需要或需要的副作用。

这有时候难以确定。希望能够记录外部副作用,并且您应该能够确定它们是否应该发生。

有时候很难确定的是,如果一个函数有内部副作用,那么很难确定。也就是说,如果它修改了一些私有状态。一个简单的例子:

class Car{
    bool engineRunning;

    public virtual void StartEngine(){
        TurnIgnitionKey();
        engineRunning = true; // this is the internal side effect
    }
    public void DriveAround(){
        if(!engineRunning)
            throw new InvalidOperationException("You have to start the engine first.");

        // implement driving around
    }
}

class S2000 : Car{
    public override void StartEngine(){
        PushStartButton();
    }
}

在此示例中,由于S2000的{​​{1}}实现未调用StartEngine()的实现,因此Car将失败。如果没有DriveAround()的源代码或非常好的文档,Car的作者可能不会知道应该调用基数的S2000例程。

出于这个原因,StartEngine不是理想的实现。更好的是:

Car

在此示例中,内部副作用受到包含在调用受保护虚拟核心实现的不可覆盖包装函数内的保护。这样,对象的状态不依赖于调用基本方法的继承者,而是决定是否在继承者手中这样做。

答案 2 :(得分:1)

没有严格的规则。有时你不想调用超类实现,有时你也不想调用 - 在超类调用之前和/或之后做一些工作。

答案 3 :(得分:0)

STL库通常不会在默认的虚函数实现中做任何非平凡的工作。他们通过用非虚拟函数包装对虚函数的调用来解决有问题的问题。这种行为假定基类的扩展器永远不会调用虚函数的默认实现(因为没有必要)。

如果基类的设计者决定扩展某些虚拟成员函数的功能,他应该编写这样的包装器,添加新功能并在那里调用虚函数,并在任何地方使用该包装器。这个概念允许扩展基类的功能和接口,而不会破坏子类。 例如,请参阅std :: streambuf。

这样基类设计师也可能会达到Alexandrescu的建议,即不公开虚拟功能。