【问题标题】:Why do both the abstract class and interface exist in C#?为什么C#中同时存在抽象类和接口?
【发布时间】:2010-11-04 22:11:01
【问题描述】:

如果我们可以通过将类中的所有成员都设为抽象来实现接口特性,为什么C#中同时存在抽象类和接口。

是不是因为:

  1. 接口存在多重继承
  2. 有接口是有意义的,因为对象的 CAN-DO 特性应该放在接口而不是基本抽象类中。

请澄清

【问题讨论】:

    标签: c# .net oop


    【解决方案1】:

    这个想法很简单 - 如果您的类 (YourClass) 已经从父类 (SomeParentClass) 派生,同时您希望您的类 (YourClass) 具有在某个抽象类 (SomeAbstractClass) 中定义的新行为),您不能通过简单地从该抽象类(SomeAbstractClass)派生来做到这一点,C# 不允许多重继承。 但是,如果您的新行为是在接口 (IYourInterface) 中定义的,您可以轻松地从接口 (IYourInterface) 以及父类 (SomeParentClass) 派生。

    考虑有一个由两个孩子(Apple & Banana)派生的类Fruit,如下所示:

    class Fruit
    {
        public virtual string GetColor()
        {
            return string.Empty;
        }
    }
    
    class Apple : Fruit
    {
        public override string GetColor()
        {
            return "Red";
        }
    }
    
    class Banana : Fruit
    {
        public override string GetColor()
        {
            return "Yellow";
        }
    }
    

    我们在 C# 中有一个现有的接口 ICloneable。这个接口只有一个方法如下图,实现这个接口的类保证可以被克隆:

    public interface ICloneable
    {
        object Clone();
    }
    

    现在,如果我想让我的 Apple 类(不是 Banana 类)可克隆,我可以像这样简单地实现 ICloneable

     class Apple : Fruit , ICloneable
    {
        public object Clone()
        {
            // add your code here
        }
    
        public override string GetColor()
        {
            return "Red";
        }
    }
    

    现在考虑你的纯抽象类论点,如果 C# 有一个纯抽象类说 Clonable 而不是接口 IClonable 像这样:

    abstract class Clonable
    {
        public abstract object Clone();
    }
    

    您现在能否通过继承抽象 Clonable 而不是 IClonable 来使您的 Apple 类可克隆?像这样:

    // Error: Class 'Apple' cannot have multiple base classes: 'Fruit' & 'Clonable'
    class Apple : Fruit, Clonable
    {
        public object Clone()
        {
            // add your code here
        }
    
        public override string GetColor()
        {
            return "Red";
        }
    }
    

    不,你不能,因为一个类不能派生自多个类。

    【讨论】:

      【解决方案2】:

      嗯,抽象类可以指定一些实现,但通常不是全部。 (话虽如此,完全有可能提供一个没有抽象成员的抽象类,但提供大量具有“无操作”实现的虚拟类)。一个接口不提供实现,只是一个契约。

      您当然可以争辩说,如果允许类的多重继承,那么接口将毫无意义。

      就我个人而言,我并不纠结于继承的“is-a”与“can-do”之间的区别。它从来没有像玩弄不同的想法并看看哪些感觉最灵活一样让我对该怎么做有很好的直觉。 (话又说回来,我是一个非常“喜欢组合而不是继承”的人......)

      编辑:正如反驳 lbushkin 在他的评论中的第三点的最方便的方式......您可以通过密封它来用非虚拟方法覆盖抽象方法(就无法进一步覆盖它而言):

      public abstract class AbstractBase
      {
          public abstract void Foo();
      }
      
      public class Derived : AbstractBase
      {
          public sealed override void Foo() {}
      }
      

      Derived 派生的类不能再覆盖Foo

      我绝不是说我想要实现的多重继承 - 但如果我们确实拥有它(以及它的复杂性),那么一个抽象类只是包含的抽象方法将完成几乎接口所做的一切。 (有显式接口实现的问题,但目前我能想到的就这些了。)

      【讨论】:

      • “优先组合而不是继承”。在某些情况下,相反的情况更可取,就像任何类型的 GUI 库一样,继承是一种祝福。恕我直言,如果有足够的层次结构,基于(正确)继承的设计比基于组合的设计更有用。但是..在任何情况下,继承都比组合难(这是我们今天使用的 OOP 的缺陷吗?)
      • “如果允许类的多重继承,接口将毫无意义”。我不同意这种说法。首先,接口提供了一定程度的解耦,即使是充满虚拟无操作方法的纯抽象基类也无法提供。它们有助于将一种类型的合同义务与不可避免的以实现为中心的问题分开。其次,由于接口没有数据成员,因此避免了数据的菱形继承问题。第三,C# 允许实现接口的方法是非虚拟的(这很有用)——抽象类不允许。
      • 对不起,Jon 不同意你关于多重继承的观点。作为一个在 C++ 中花费了很多时间的人,相信我,这太可怕了。大多数 C++ 开发人员现在使用与 c#/java 几乎相同的方式使用多重继承,即一个主基类加上其他所有内容作为“mix-ins”
      • @LBushkin:第 1 点:如果你有一个带有纯虚方法的抽象基类,那么它与你的耦合比接口多吗?第 2 点:是的,我并不是说多重继承没有它的缺点,我也没有要求它……但是您可以编写一个带有纯虚方法和 no 字段的抽象类。第 3 点:您可以密封基类的虚拟方法:我将编辑我的帖子以显示这一点。
      【解决方案3】:

      这两种机制存在的一个重要原因是,c#.NET 只允许单继承,而不是像 C++ 那样的多继承。类继承允许你只从一个地方继承实现;其他一切都必须通过实现接口来完成。

      例如,假设我创建了一个类,例如 Car,并且我将子类分为三个子类:RearWheelDrive、FrontWheelDrive 和 AllWheelDrive。现在我决定我需要沿着不同的“轴”削减我的课程,比如那些有按钮启动器的和那些没有的。我希望所有按钮启动汽车都具有“PushStartButton()”方法,非按钮汽车具有“TurnKey()”方法,并且我希望能够处理 Car 对象(关于启动它们),而不管哪个子类他们是。我可以定义我的类可以实现的接口,例如 IPushButtonStart 和 IKeyedIgnition,因此我有一个通用的方法来处理我的对象,这些对象的不同之处与每个派生的单个基类无关。

      【讨论】:

        【解决方案4】:

        接口用于一个类可以做的事情,但它也用于隐藏一个类可以做的一些事情。

        例如IEnumerable<T> 接口描述了一个类可以遍历它的成员,但它也限制了对这个单一能力的访问。 List<T> 也可以通过索引访问项目,但是当你通过IEnumerable<T> 接口访问它时,你只知道它具有迭代成员的能力。

        如果一个方法接受IEnumerable<T> 接口作为参数,这意味着它只对遍历成员的能力感兴趣。您可以使用具有此功能的多个不同类(如List<T> 或数组T[]),而无需为每个类使用一个方法。

        一个方法不仅可以接受几个不同的实现接口的类,还可以创建实现接口的新类,并且该方法也很乐意接受这些类。

        【讨论】:

          【解决方案5】:

          这不是一个微不足道的问题,这是一个非常好的问题,我总是问我面试的任何候选人。
          简而言之 - 抽象基类定义类型层次结构,而接口定义契约。

          您可以将其视为 是一个 vs 实现一个
          IE Account 可以是一个抽象基类帐户,因为您可以有一个CheckingAccount、一个SavingsAccount 等等,它们都派生自抽象基类Account。抽象基类也可以像任何普通类一样包含非抽象方法、属性和字段。然而,接口包含必须实现的抽象方法和属性。

          c# 让你只从一个基类派生——单继承,就像 java。但是,您可以实现任意数量的接口 - 这是因为接口只是您的类承诺实现的契约。

          所以,如果我有一个类 SourceFile,那么我的类可以选择实现 ISourceControl,上面写着“我忠实地承诺实现 ISourceControl 需要的方法和属性”

          这是一个很大的领域,可能比我给出的帖子更值得发布,但是我的时间很短,但我希望能有所帮助!

          【讨论】:

            【解决方案6】:

            它们都存在,因为它们都是非常不同的东西。抽象类允许实现,而接口则不允许。接口非常方便,因为它可以让我说出我正在构建的类型(它是可序列化的、可食用的等),但它不允许我为我定义的成员。

            抽象类比接口更强大,因为它允许我通过抽象和虚拟成员创建继承接口,但如果我愿意,还可以提供某种默认或基本实现。然而,正如蜘蛛侠所知,随着抽象类在架构上更加脆弱,这种强大的力量带来了巨大的责任。

            旁注: 值得注意的是,Vance Morrrison(CLR 团队的成员)推测在未来版本的 CLR 中向接口添加默认方法实现。这将极大地模糊接口和抽象类之间的区别。详情请见this video

            【讨论】:

              【解决方案7】:

              它们有两个截然不同的目的。

              抽象类提供了一种从定义的契约继承对象的方法,并允许在基类中指定行为。从理论上讲,这提供了 IS-A 关系,因为具体类是基类的特定类型。

              接口允许类定义一个(或多个)它们将履行的契约。它们允许 ACTS-AS 或“可以用作”类型的关系,而不是直接继承。这就是为什么接口通常会使用形容词作为名称 (IDisposable) 而不是名词。

              【讨论】:

                【解决方案8】:

                abstract class 可以有一个实现,而interface 只允许您创建一个实现者必须遵循的合同。使用抽象类,您可以为它们的子类提供通用的行为,而使用接口则不能。

                【讨论】:

                  【解决方案9】:

                  接口定义了实现类必须履行的契约;这是一种说明“这样做”的方式。抽象类是根据定义不完整的类的部分实现,需要完成派生。它们是非常不同的东西。

                  【讨论】:

                  • 抽象类的实现实际上可能已经完成了——不需要它有任何抽象成员。从抽象类派生而不覆盖任何东西很少有用,但它是完全合法的。
                  • 啊,好点;仍然,“抽象类”的定义意味着它不能被实例化,所以必须有一个派生来实例化它;我仍然认为这是“不完整的”,因为您不能单独使用它。不过,您的观点非常相关,而且很好理解。
                  【解决方案10】:

                  接口的存在是为了提供一个没有任何实现的类,因此 .NET 可以为托管环境中的安全和功能性多重继承提供支持。

                  【讨论】:

                    【解决方案11】:

                    你已经给出了很好的答案。我认为你的第二个答案是真正的原因。如果我想使对象具有可比较性,则不必从可比较基类派生。如果您考虑所有接口,请考虑处理 IComparable 等基本接口所需的所有排列。

                    接口让我们围绕对象提供的公开行为定义契约。抽象类让您可以定义行为和实现,这是完全不同的事情。

                    【讨论】:

                      猜你喜欢
                      • 2011-07-14
                      • 1970-01-01
                      • 2014-01-02
                      • 1970-01-01
                      • 2011-01-23
                      • 1970-01-01
                      • 2011-07-05
                      • 2010-09-20
                      • 2012-05-23
                      相关资源
                      最近更新 更多