如何“正确地”重写基类方法?

36

每当我重写基类的方法时,除了我的实现之外,似乎有三个选择:

1)调用base.Method(),然后提供我的实现。

2)先提供我的实现,然后调用base.Method()

3)只提供我的实现。

最近在使用一个库时,我发现由于未按照库的期望实现方法而引入了一些错误。 我不确定这是库方面的问题还是我的理解有误。

我会举一个例子。

public class ViewManager {
     public virtual void Customize(){
        PrepareBaseView();
     }
}

public class PostViewManager {
     public override void Customize(){
        base.Customize();
        PreparePostView();
     }
}


public class PreViewManager {
     public override void Customize(){
        PreparePreView();
        base.Customize();
     }
}


public class CustomViewManager {
     public override void Customize(){
        PrepareCustomView();
     }
}

我的问题是,子类如何知道(不查看基类实现的情况下)父类期望的顺序(或选项)是什么? 有没有一种方式可以让父类强制所有派生类中的一个选项?


1
PostViewManagerPreViewManagerCustomViewManager 是否继承自 ViewManager?如果是,您应该编辑代码。 - LaTeX
4个回答

40

子类如何在不查看父类实现的情况下知道父类期望哪个顺序(或选项)?

当你子类化和覆盖一个方法时,没有办法“知道”这一点。这里唯一的选择就是适当地编写文档。

有没有一种方式让父类强制规定三个备选方案中的一个适用于所有派生类?

这里唯一的选择是避免这个问题。不要让子类覆盖该方法,可以将其声明为非虚拟,然后在适当的位置调用虚拟方法。例如,如果您想强制子类“先调用您的版本”,则可以这样做:

public class BaseClass {
    public void Method() // Non-virtual
    {
          // Do required work

          // Call virtual method now...
          this.OnMethod();
    }

    protected virtual void OnMethod()
    { // Do nothing
    }
 }

子类可以“覆盖”OnMethod并提供在“method”工作之后发生的功能。

这样做的原因是虚方法被设计成允许子类完全替换父类的实现。这是有意为之的。如果你想防止这种情况,最好将方法设置为非虚拟的。


1
你比我先完成了。这是“脆弱基类”问题。对于你的解决方法(也就是那个你比我先完成的),即“模板方法模式”,我给你一分。你甚至可以将受保护的OnMethod方法声明为抽象的,以便派生类知道它们必须提供自己的实现,而且更重要的是,它们不必调用基本实现(假设不能实例化“Base”)。 - anton.burger
+1 这将使你免受其他开发人员的狂暴攻击。 - Marc
@shambulator - 覆盖是可选的,所以将OnMethod定义为抽象方法可能并不可比较。但这仍然是一个不错的选择。 - Nelson Rothermel
你的例子帮助我理解了我的问题...谢谢。 - AceMark
1
@Dummy:如果你在子类中定义了一个与父类同名的方法,并且没有将其更改为虚方法,那么你必须在子类中使用 new 关键字。此外,调用哪个方法取决于你转换成的类型。换句话说,你可以有 var x = new SubClass(),在这种情况下,((SubClass)x).Method() 将调用子类方法,而 ((BaseClass)x).Method() 将调用基类方法并跳过子类。这不允许扩展性而不改变现有代码来使用新类型,因此它与 virtual/override 不可比。 - Nelson Rothermel
显示剩余3条评论

3
这就是为什么我认为当你将虚拟方法放在库中时,它们是危险的。实际上,如果不查看基类,有时候你永远无法确切知道。有时候你必须启动反编译器,阅读文档或采用试错方法。

自己编写代码时,我总是尽力遵循以下规则:

覆盖受保护的虚拟方法的派生类不需要调用基类实现。即使未调用其实现,基类也必须继续正确工作。

这摘自http://msdn.microsoft.com/en-us/library/ms229011.aspx,但这是针对事件设计的,尽管我相信我在Framework Design Guidelines书中也读到过(http://www.amazon.com/Framework-Design-Guidelines-Conventions-Libraries/dp/0321246756)。

但是,显然并非如此,例如ASP.NET Web Forms需要在Page_Load上使用基础调用。

总之,情况各异,不幸的是没有立即知道的方法。如果我有疑问,我会最初省略该调用。


1

简短的回答是不行。您无法强制子类以任何顺序调用基类方法,或者根本不调用。

从技术上讲,这些信息应该包含在基对象的文档中。如果您绝对必须在子类代码之前或之后运行一些代码,则可以执行以下操作:

1)在基类中创建一个非虚函数。我们称其为MyFunction

2)在基类中创建一个受保护的虚函数。我们称其为_MyFunction

3)让派生类扩展_MyFunction方法。

4)让MyFunction调用_MyFunction并运行它需要在调用之前或之后运行的代码。

这种方法很丑陋,需要大量额外的代码,因此我建议只在文档中放置通知。


我想知道是否有类似PostSharp的工具可以强制执行这种排序。 - FrustratedWithFormsDesigner

0
基类的要求应该由库设计者进行文档化记录。 这个问题是一些库主要包含密封类的原因。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接