【问题标题】:Abstract classes vs Interfaces抽象类与接口
【发布时间】:2010-12-01 05:57:58
【问题描述】:

我对 C# 中抽象类的使用有点困惑。在 C++ 中,定义继承抽象类的类可以遵循的模板是有意义的。但是,在 C# 中,接口不是起到同样的作用吗?

确实,抽象类可以具有接口不提供的默认实现。所以如果实现不需要包含在基类中,是不是更好的接口?

【问题讨论】:

标签: c#


【解决方案1】:

我仍然喜欢提供一个接口的默认抽象实现,假设它是一个实体接口(而且它是有意义的)。你永远不知道什么时候可以向接口添加一些具有简单默认实现的东西,可以包含并“免费”提供给从抽象基类继承的任何人。

【讨论】:

  • 一个帖子投了反对票就跑了,而没有说明他们背后的理由,这真的很不勇敢。
  • 扩展方法允许将实现的方法添加到接口。这消除了抽象类的一些(但不是全部)优点。
  • @CodeInChaos:虽然扩展方法可以解释抽象类与接口的一些用例,但它们不是替代品。扩展方法不允许多态性,要求它们的声明命名空间与使用代码相同或在显式 using (VB Includes) 声明中,并且不能访问任何非公共成员。扩展方法让您可以更轻松地定义功能,否则您可能会在类的外部复制这些功能。
【解决方案2】:

这个CodeProject article 有很多关于两者之间差异的信息,包括一个比较和对比每个特征的表格。

接口定义了类之间的契约——类相互调用的方式。一个类可以实现多个接口,但只能从一个抽象类继承。

【讨论】:

    【解决方案3】:

    确实,抽象类可以具有接口不提供的默认实现。因此,如果不需要将实现包含在基类中,是否最好选择接口?

    是的:)。如果在基类中实现某些对 all 继承类通用的方法是有意义的,则应该使用抽象类。如果基类只用于定义接口,而继承的类之间没有通用逻辑,则使用接口。

    【讨论】:

    • 很少是“非此即彼”。很多时候,您会发现具有共同祖先的类与实现相同接口的不同祖先的类混合在一起。
    • 没错,这只是我倾向于遵循的准则。如果对所有祖先都有一些基本逻辑是有意义的,我会使用抽象类,否则我会使用接口。
    • ChrisF 也提出了一个很好的观点;您不能从多个类继承,但可以继承多个接口。当有这么多用例时,很难定义通用规则。
    • 谢谢,您的回答让我更好地理解了抽象类,因为我习惯于在实现对象时使用接口。然而还需要提到的是,我只使用接口来表示数据,就好像它是一个数据表一样,并将我的方法编码在工厂内部类后面,然后通过外观公共静态类调用它们。这一定是我从来不用抽象类的原因。
    • @Ed:我的意思并不是说你必须在两者之间做出选择。通过同时拥有这两者,您可以获得最大的灵活性(以及相应的最高维护成本)。您定义接口,然后创建一个提供接口默认实现的抽象类。这允许继承层次结构的便利性和接口的灵活性。
    【解决方案4】:

    接口和抽象类服务于不同的目标。接口用于声明类的契约,而抽象类用于共享公共实现。

    如果你只使用抽象类,你的类不能从其他类继承,因为 C# 不支持多重继承。如果你只使用接口,你的类不能共享公共代码。

    public interface IFoo
    {
        void Bar();
    }
    
    public abstract class FooBase : IFoo
    {
        public abstract void Bar()
        {
            // Do some stuff usually required for IFoo.
        }
    }
    

    现在我们可以在各种情况下使用接口和基础实现了。

    public class FooOne : FooBase
    {
        public override void Bar()
        {
            base.Bar(); // Use base implementation.
    
            // Do specialized stuff.
        }
    }
    
    public class FooTwo : FooBase
    {
        public override void Bar()
        {
            // Do other specialized stuff.
    
            base.Bar(); // Use base implementation.
    
            // Do more specialized stuff.
        }
    }
    
    // This class cannot use the base implementation from FooBase because
    // of inheriting from OtherClass but it can still implement IFoo.
    public class FooThree : OtherClass, IFoo
    {
        public virtual void Bar()
        {
            // Do stuff.
        }
    }
    

    【讨论】:

      【解决方案5】:

      第一个问题,是的。

      对于你的第二个答案,我会给你一些我遵循的提示。

      • 结合使用抽象类和接口来优化您的设计权衡。

      使用抽象类

      • 当创建一个将被广泛分布或重用的类库时——尤其是对于客户端,使用抽象类而不是接口;因为,它简化了版本控制。

      • 使用抽象类为类型族定义公共基类。

      • 使用抽象类提供默认行为。

      • 仅继承该类逻辑所属层次结构中的基类。

      使用界面

      • 创建可随意更改的独立项目时,使用接口优先于抽象类;因为,它提供了更多的设计灵活性。

      • 使用接口引入多态行为而无需子类化,并为多重继承建模——允许特定类型支持多种行为。

      • 使用接口为值类型设计多态层次结构。

      • 在真正需要不可变合约时使用接口。

      • 设计良好的界面定义了非常具体的功能范围。拆分包含不相关功能的接口。

      【讨论】:

        【解决方案6】:

        您可以实现任意数量的接口,但只能继承一个类。所以类和接口在 C# 中是完全不同的野兽,你不能互换使用它们。在 C# 中,抽象类仍然是类,而不是接口。

        【讨论】:

          【解决方案7】:

          如果您没有任何默认/通用代码,请使用接口。

          抽象类也可以作为模板,定义一些算法的步骤和调用顺序,派生类提供这些步骤的实现:

          public abstract class Processor
          {
            // this is the only public method
            // implements the order of the separate steps
            public void Process()
            {
              Step1();
              Step2();
              //... 
            }
            // implementation is provided by derived classes
            protected abstract void Step1();
            protected abstract void Step2();
          }
          

          【讨论】:

          • 从 Java 8 开始,接口可以有默认实现。那么如何决定何时使用抽象类和接口呢?
          【解决方案8】:

          虽然没有实现的抽象类确实等同于接口,但接口和抽象类用于不同的事物。

          接口可用于最一般意义上的多态性。例如,ICollection 用于定义所有集合的接口(有不少)。在这里,它定义了您要对某种类型执行的操作。还有许多其他用途(例如可测试性、依赖注入等)。此外,界面可以混合使用,这在概念上和技术上都有效。

          抽象类更多地与可模板化的行为有关,其中虚拟方法是“填补空白”的地方。显然你不能混合抽象类(至少在 C# 中不能)。

          【讨论】:

            【解决方案9】:

            在 C# 中,使用抽象类的一大障碍是只能使用一个。使用接口,您的优点是不限制实现的基类。为此,我总是使用接口,即使我创建了一个抽象基类来帮助实现。

            基本抽象类的另一个烦恼通常是它们倾向于依赖模板参数。这会使您的其余代码很难使用。解决这个问题的简单方法是提供一个与抽象类对话的接口,而无需知道模板类的类型参数。

            其他人似乎输入他们的答案更快,但请允许我总结一下......

            使用接口。如果需要共享实现,还可以创建一个抽象基类,提供通用的实现细节。

            【讨论】:

              【解决方案10】:

              请注意,使用 C#3,您可以通过使用扩展方法为接口提供默认行为。但是,有一些限制,抽象类仍然有其一席之地。

              【讨论】:

              • 你不能 - 扩展方法不实现实现该接口的类中省略的接口成员。你可以为接口提供额外的行为,但那是非多态的,这在OOP中有点弄巧成拙。
              • @Pavel - 虽然扩展确实没有实现接口成员,但它们仍然可以提供有价值的行为。请参阅 Bill Wagner 的《更有效的 C#》一书中的技巧或以下有关 Mixins 的文章。 zorched.net/2008/01/03/…
              【解决方案11】:

              我在建模时遵循的规则是: 类(包括抽象)和结构模型实体。接口模型行为。 实现接口的实体可以被视为展示接口(合同)公开的行为。

              【讨论】:

                【解决方案12】:

                这在一些答案中有所暗示,但没有明确说明。

                您可以实现多个接口并且只能从一个基类继承,就好像它们是同一枚硬币的两个面一样,这不是一个好方法。

                不要将接口视为对象层次结构的一部分。它们通常只是您的真实对象层次结构可以声明为实现的功能的一小部分(或者至少是特定的,如果不是很小的话)。以 IDisposable 为例。如果你是写这个的人,你会问自己它应该是一个抽象类还是一个接口?很明显,在这种情况下,它们是两个完全不同的东西。我想成为一次性的。想想 ICloneable 和 IEnumerable。你可以在你的类中实现这些,而不必尝试让你的类派生自一些不相关的类,如 List 或 Array。或采取 IEnumerator。只需为对象提供 MoveNext 类型的视图。我的类可以提供该功能,而不必笨拙地从与我的类无关的其他顺序集合数据类型派生。

                【讨论】:

                  【解决方案13】:

                  我总是更喜欢接口,只要基类没有一些真正“繁重”的实现,这将为实现者节省大量时间。 给那个 .net 只允许一个基类继承,强迫你的用户继承是一个巨大的限制。

                  【讨论】:

                    【解决方案14】:

                    你应该总是更喜欢编程而不是接口而不是具体的类。

                    如果您还想要一个默认实现,您仍然可以创建一个基类来实现您的接口。

                    【讨论】:

                      猜你喜欢
                      • 2017-02-19
                      • 2016-03-24
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2010-11-08
                      • 2011-05-22
                      相关资源
                      最近更新 更多