代码之家  ›  专栏  ›  技术社区  ›  alsor.net

override method-调用overriden实现(super)还是不调用?

  •  3
  • alsor.net  · 技术社区  · 15 年前

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

    4 回复  |  直到 13 年前
        1
  •  3
  •   jsight TaherT    15 年前

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

    一个例外是你提前知道超类会做你特别不想做的事情。但不管怎样,这些问题都指向设计问题。看到子类不调用它们的父类是非常可疑的。

        2
  •  3
  •   P Daddy    13 年前

    在重写基类的一个方法时调用它的实现有两个主要原因:

    您希望扩展基类的行为。

    在这种情况下,通常更容易依赖基类来执行工作的首当其冲,并且只在顶部添加额外的工作。这个决定通常很容易做出。

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

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

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

    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 的实现 StartEngine() 不叫 Car 的实现,然后 DriveAround() 会失败。没有源代码 小型车 或非常好的文档,作者 S2000 可能不知道基地 StartEngine 应该调用例程。

    因此, 小型车 不是理想的实现。最好是这样:

    class Car{
        bool engineRunning;
    
        public void StartEngine(){ // not virtual
            StartEngineInternal();
            engineRunning = true;
        }
        protected virtual void StartEngineInternal(){
            TurnIgnitionKey();
        }
    }
    

    在本例中,内部副作用是通过包含在调用受保护的虚拟核心实现的不可重写包装函数中来保护的。这样,对象的状态就不依赖于调用基方法的继承者,而是由它所属的继承者来决定是否这样做。

        3
  •  1
  •   Vinay Sajip    15 年前

    没有硬性规定。有时,您不想调用超类实现,而另一些时候,您会这样做——在超类调用之前和/或之后做一些工作。

        4
  •  0
  •   Basilevs    15 年前

    在虚拟函数的默认实现中,STL库通常不做任何重要的工作。它们通过用非虚拟函数包装对虚拟函数的调用来解决问题。这种行为假定基类的扩展程序永远不会调用虚拟函数的默认实现(因为不需要这样做)。

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

    这样,基类设计器也可以达到Alexandrescu不公开虚拟函数的建议。