我应该使用抽象方法还是虚拟方法?


11

如果我们假设不希望基类成为纯接口类,并使用下面的两个示例,那么使用抽象或虚拟方法类定义是更好的方法吗?

  • “抽象”版本的优点是它看起来更简洁,并强制派生类提供希望的有意义的实现。

  • “虚拟”版本的优点是可以轻松地由其他模块插入并用于测试,而无需像抽象版本那样添加大量底层框架。

摘要版本:

public abstract class AbstractVersion
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

虚拟版本:

public class VirtualVersion
{
    public virtual ReturnType Method1()
    {
        return ReturnType.NotImplemented;
    }

    public virtual ReturnType Method2()
    {
        return ReturnType.NotImplemented;
    }
             .
             .
    public virtual ReturnType MethodN()
    {
        return ReturnType.NotImplemented;
    }

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

为什么我们假设不希望使用接口?
安东尼·佩格拉姆

没有部分问题,很难说一个比另一个更好。
Codism

@Anthony:不希望使用接口,因为此类中也包含有用的功能。
Dunk

4
return ReturnType.NotImplemented?认真吗 如果不能在编译时拒绝未实现的类型(可以;可以使用抽象方法),则至少会引发异常。
2013年

3
@Dunk:这必要的。返回值被取消选中。
Jan Hudec

Answers:


15

如果我消耗您的东西,那么我的投票将是抽象方法。伴随着“早期失败”。在声明时添加所有方法可能会很痛苦(尽管任何体面的重构工具都可以快速完成此操作),但是至少我知道问题出在哪里并立即解决。我宁愿稍后调试六个月零十二个人的更改,以查看为什么我们突然收到未实现的异常。


关于意外收到NotImplemented错误的好点。在抽象方面,这是一个加号,因为您将获得编译时而不是运行时错误。
Dunk

3
+1-除了尽早失败之外,继承者还可以立即通过“实现抽象类”查看他们需要实现哪些方法,而不必执行他们认为足够的工作,然后在运行时失败。
Telastyn

我接受了这个答案,因为在编译时失败是我无法列出的一个优点,并且因为提到使用IDE快速自动实现方法。
Dunk

25

虚拟版本既容易出错,又在语义上不正确。

摘要说:“此处未实现此方法。必须使它实现才能使此类工作”

Virtual在说“我有一个默认实现,但是如果需要,可以更改我”

如果您的最终目标是可测试性,那么接口通常是最佳选择。(此类执行x而不是此类执行)。您可能需要将您的类分解为较小的组件,以使其正常工作。


3

这取决于您的班级用法。

如果这些方法具有某种合理的“空”实现,则您有很多方法,并且通常仅覆盖其中的一些virtual方法,那么使用方法才有意义。例如ExpressionVisitor以这种方式实现。

否则,我认为您应该使用abstract方法。

理想情况下,您不应有未实现的方法,但是在某些情况下,这是最好的方法。但是,如果您决定这样做,则此类方法应该throw NotImplementedException,而不返回某些特殊值。


我会注意到“ NotImplementedException”通常表示遗漏错误,而“ NotSupportedException”则表示明显的选择。除此之外,我同意。
Anthony Pegram

值得注意的是,许多方法是根据“尽一切可能满足与X相关的任何义务”定义的。在没有与X相关的项目的对象上调用这种方法可能毫无意义,但是仍然可以很好地定义。此外,如果一个对象可能有或可能没有任何与X相关的义务,通常比先询问该对象是否有任何义务并有条件地要求其履行义务,更无条件地说“满足您与X相关的所有义务”更为干净和有效。 。
2015年

1

我建议您重新考虑定义一个由基类实现的单独接口,然后按照抽象方法进行操作。

映像代码如下:

public interface IVersion
{
    ReturnType Method1();        
    ReturnType Method2();
             .
             .
    ReturnType MethodN();
}

public abstract class AbstractVersion : IVersion
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

这样做可以解决以下问题:

  1. 通过使用所有使用从AbstractVersion派生的对象的代码,现在可以实现接收IVersion接口,这意味着可以更轻松地对它们进行单元测试。

  2. 然后,产品的版本2可以实现接口IVersion2,以提供其他功能而不会破坏您现有的客户代码。

例如。

public interface IVersion
{
    ReturnType Method1();        
    ReturnType Method2();
             .
             .
    ReturnType MethodN();
}

public interface IVersion2
{
    ReturnType Method2_1();
}

public abstract class AbstractVersion : IVersion, IVersion2
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();
    public abstract ReturnType Method2_1();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

另外,还值得一读有关依赖项倒置的内容,以防止此类包含硬编码的依赖项,从而阻止有效的单元测试。


我赞成提供处理不同版本的功能。但是,作为设计的正常部分,我反复尝试过有效地利用接口类,并且最终最终意识到接口类要么不提供任何值,要么不提供任何值,而实际上使代码变得晦涩难懂,而不是使生活更轻松。我很少会有多个类从另一个类(例如接口类)继承而来,并且没有很多不可共享的通用性。当我获得了接口不提供的内置共享方面时,抽象类往往会工作得更好。
Dunk 2014年

与具有良好类名的继承相比,提供一堆直观的方法可以轻松地理解类(以及系统),而不是一堆很难在整体上结合在一起的功能接口类名。同样,使用接口类往往会创建大量额外的类,从而使系统难以以另一种方式理解。
Dunk 2014年

-2

依赖注入依赖于接口。这是一个简短的例子。Class Student具有一个名为CreateStudent的函数,该函数需要一个实现接口“ IReporting”(带有ReportAction方法)的参数。创建学生后,它将在具体的类参数上调用ReportAction。如果将系统设置为在创建学生后发送电子邮件,我们将发送一个具体类,该类在其ReportAction实现中发送电子邮件,或者我们可以发送另一个具体类,将其输出输出到其ReportAction实现中的打印机。非常适合代码重用。


1
这似乎并没有提供任何对先前5个答案所提出和解释的观点的实质性建议
gna
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.