在C#中实现多态性,最佳方法是什么?

3

这是我第一次提问,希望大家温柔点!

最近几天,我一直在阅读有关多态性的文章,并尝试将其应用到我的c#编程中。看起来有几种不同的实现方式。我希望我已经掌握了这个概念,但即使我没有,也很高兴能够得到澄清。

从我所看到的,我有三个选择:

  1. 我可以继承一个基类,并在我想要派生类重写的任何方法上使用关键字“virtual”。
  2. 我可以实现一个抽象类,其中包含虚拟方法,然后通过这种方式实现。
  3. 我可以使用接口?

据我所见,如果我不需要在基类中添加任何实现逻辑,那么接口会给我最大的灵活性(因为我不会受到多重继承等方面的限制),但如果我需要在基类中执行某些操作,那么选择1或2可能是更好的解决方案?

感谢各位对此的任何意见 - 我这个周末在这个网站和其他地方都读了很多,我我现在理解了这些方法,但我只想用具体的语言来澄清一下自己是否正确。希望我也标记得正确。

谢谢, Terry

6个回答

6

接口提供了最高的抽象层次;您不会被绑定到任何特定的实现(如果实现必须由于其他原因具有不同的基类,则非常有用)。

对于真正的多态性,virtual是必须的;多态性通常与类型子类化相关...

当然,您可以混合使用这两种方法:

public interface IFoo {
    void Bar();
}
class Foo : IFoo {
    public virtual void Bar() {...}
}
class Foo2 : Foo {
    public override ...
} 

abstract 是一个单独的问题;选择 abstract 的关键是:它是否能被基类合理地定义?如果没有默认实现,那么它必须是 abstract

当有许多实现细节是共同的,并且通过接口复制是毫无意义时,共同的基类可以很有用;但有趣的是 - 如果实现永远不会因每个实现而异,则扩展方法提供了一种在 interface 上公开此内容的有用方式(这样每个实现就不必这样做):

public interface IFoo {
    void Bar();
}
public static class FooExtensions {
    // just a silly example...
    public static bool TryBar(this IFoo foo) {
        try {
             foo.Bar();
             return true;
        } catch {
             return false;
        }
    }
}

1
我喜欢使用这种方式来创建扩展方法以产生重载。这样我的接口易于实现,并且所有实现都可以以相同的方式处理重载。 - Jacob Stanley
我真的很喜欢Marc的解释,你和下面的Yann都花了很多心思来解释答案,谢谢 - 当然我会等待看看还有什么其他的意见,但是谢谢 - 这真的很有帮助! - Terry_Brown
@Jacob - 的确;它对于可选参数非常好(直到我们使用C# 4.0)- 例如,我倾向于在接口上有“分页”版本的查询方法,并使用扩展方法来提供默认的“获取所有”(更简单)重载 - 然后我只需要在具体的站点实现1个方法,而不是2/3/4等。 - Marc Gravell
对我来说,这个回答是最好的 - 我仍然很想看到其他人对这个主题的看法,但上面的回答确实帮了我很多,谢谢Marc :) - Terry_Brown

2

接口通常受到青睐,原因如下:

  • 多态性是关于契约的,继承是关于重用的
  • 继承链很难正确实现(特别是单一继承,例如 Windows Forms 控件中的设计错误,其中像可滚动性、富文本等功能都在继承链中硬编码)
  • 继承会导致维护问题

话虽如此,如果您想利用共同的功能,则可以使用接口进行多态性(使您的方法接受接口),但使用抽象基类来共享某些行为。

public interface IFoo
{
    void Bar();
    enter code here
}

将成为您的界面
public abstract class BaseFoo : IFoo
{
    void Bar
  {
        // Default implementation
  }
}

将是您的默认实现。

public class SomeFoo : BaseFoo
{

}

这是一个可以重复使用实现的类。

但是,您将使用接口来实现多态性:

public class Bar
{
   int DoSometingWithFoo(IFoo foo)
{

    foo.Bar();
}
}

请注意,在该方法中我们使用了接口。

我也非常喜欢你在这篇帖子开头的总结,Yann。我认为它有助于在高层次上集中思想。 - Terry_Brown

2

以上三种方法都是有效的,各有其优点。并没有一种技术是“最好”的。只有编程实践和经验才能帮助您在正确的时间选择合适的技术。

因此,现在选择一个看起来合适的方法,并开始实施。观察哪些方法有效,哪些失败,吸取教训,再试一次。


1
你应该首先问自己“为什么需要使用多态性?”因为多态性本身并不是目的,而是达到目的的手段。一旦你明确定义了问题,就应该更清楚应该采用哪种方法。
无论如何,你所评论的这三种方法并不是互斥的,如果你需要在某些类之间重用逻辑但不需要在其他类中重用,或者需要一些不同的接口,仍然可以混合使用它们...

完全正确 - 我猜上面的内容并不是为了使用多态而多态,更多是为了理解方法,并在何时使用每种方法(或者像你说的那样,混合和匹配以适应)。 - Terry_Brown

1
  • 使用抽象类来强制实现类结构
  • 使用接口来描述行为

0

这真的取决于您想如何构建代码以及您想要用它做什么。

从测试的角度来看,拥有一个类型为Interface的基类是很好的,因为您可以使用模拟对象来替换它。

如果您希望在某些函数中实现代码而在其他函数中不实现,则抽象类非常适合,因为如果抽象类除了抽象函数之外没有任何内容,那么它就是一个接口。

请记住,抽象类不能被实例化,因此对于工作代码,您必须有一个派生自它的类。

实际上,所有方法都是有效的。

如果我有许多从它派生的类但只有浅层次(例如只有1个类),我倾向于使用抽象类。

如果我期望有深层次的继承,那么我会使用具有虚函数的类。

无论哪种方式,最好保持类简单,以及它们的继承,因为它们变得越复杂,引入错误的可能性就越大。


除此之外:“因此,要使用有效的代码,您必须有一个从中派生的类” - 实际上,有方法可以在没有任何具体实现的情况下有用地同时使用接口和抽象类...虽然这是一个边缘案例,但通过使用表达式来指示意图(在RPC等方面很有用),而不必调用任何方法,是可行的。 - Marc Gravell

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