【问题标题】:When to use an interface instead of an abstract class and vice versa?何时使用接口而不是抽象类,反之亦然?
【发布时间】:2010-10-03 12:13:52
【问题描述】:

这可能是一个通用的 OOP 问题。我想根据它们的用法在接口和抽象类之间进行通用比较。

什么时候需要使用接口,什么时候需要使用抽象类

【问题讨论】:

标签: oop inheritance interface abstract-class


【解决方案1】:

如果您想提供一些基本实现,请使用抽象类。

【讨论】:

  • 谢谢塞巴斯蒂安。但是如果我不需要一个基本的实现呢?如果这是它们之间的唯一区别,那么抽象类和接口不会相同吗?为什么会有区别?
  • 因为有些语言没有接口——C++。
【解决方案2】:

在 java 中,您可以从一个(抽象)类继承来“提供”功能,并且您可以实现许多接口来“确保”功能

【讨论】:

  • lil' 提示:如果要从抽象类和接口继承,请确保抽象类实现了接口
【解决方案3】:

抽象类可以具有共享状态或功能。接口只是提供状态或功能的承诺。一个好的抽象类将减少必须重写的代码量,因为它的功能或状态可以共享。该接口没有定义要共享的信息

【讨论】:

  • 对我来说,这是最好的答案,遗憾的是它没有被投票得更高。是的,这两个概念在哲学上存在差异,但根源在于抽象类确保所有后代共享功能/状态,而接口仅确保共同的纽带。
  • 例如,抽象基类用于模板方法设计模式,而接口用于策略设计模式。
  • 我认为 Jorge 的总结解释了他们俩存在的主要但背后的原因,而 Alex 的回答是结果的差异。我希望我能将两者都标记为正确答案,但我仍然更喜欢 Jorge 的答案。
  • Here 是带有代码的示例
  • 对我来说,“一个好的抽象类将减少必须重写的代码量,因为它的功能或状态可以共享。”陈述是答案的核心。
【解决方案4】:

这可能是一个非常困难的电话......

我可以给出一个指针:一个对象可以实现许多接口,而一个对象只能继承一个基类(在像 c# 这样的现代 OO 语言中,我知道 C++ 具有多重继承——但这不是不受欢迎吗?)

【讨论】:

  • 多重继承使得 Mixin 的实现无缝衔接,编写良好的 Mixin 使用起来轻而易举,但很难实现,而且很难在没有任何不足的情况下编写。尽管 IMO,Mixin 整体上还是很酷的。
  • 实际上我没有,多重继承确实是我们这些极客之间的一个肯定的辩论火花,我认为绝对没有理由反对。事实上,我赞成你的回答。
  • 我试图指出的唯一一点是,在具有单一继承的语言中混入的方法也是可能的(C#、PHP、javascript),但通过hacky 行为或俗气的语法。当他们工作时我喜欢 Mixin,但我仍然不确定是否要使用多重继承。
  • 这个答案更多的是句法差异而不是设计差异。我认为他要求的是设计差异
【解决方案5】:

抽象类可以有实现。

接口没有实现,它只是定义了一种契约。

也可能存在一些与语言相关的差异:例如 C# 没有多重继承,但可以在一个类中实现多个接口。

【讨论】:

  • 当您说“一种合同”时,您的意思是像在 Web 服务中一样吗?
  • 从技术上讲,Web 服务不适用于接口。对于合同,我的意思是对象的用户知道该对象上存在哪些方法。例如,IMouse 接口将有一个 Move 方法,以及一个鼠标左键和右键事件。
【解决方案6】:

答案因语言而异。例如,在 Java 中,一个类可以实现(继承)多个接口,但只能从一个抽象类继承。所以接口给你更多的灵活性。但在 C++ 中,情况并非如此。

【讨论】:

    【解决方案7】:

    纯粹在继承的基础上,您将使用一个抽象,其中您明确定义了后代、抽象关系(即动物->猫)和/或需要虚拟或非公共继承属性,尤其是共享状态(接口不支持)。

    你应该尽量支持组合(通过依赖注入)而不是继承,并注意接口作为契约支持单元测试、关注点分离和(语言变化)多重继承,而 Abstracts 不能。

    【讨论】:

      【解决方案8】:

      我为此写了一篇文章:

      Abstract classes and interfaces

      总结:

      当我们谈论抽象类时,我们是在定义对象类型的特征;指定对象是什么

      当我们谈论一个接口并定义我们承诺提供的功能时,我们谈论的是建立一个关于对象可以做什么的合同。

      【讨论】:

      • 这很有帮助:Interfaces do not express something like "a Doberman is a type of dog and every dog can walk" but more like "this thing can walk"。谢谢
      • 你的链接好像失效了。
      • Alex 在下面的解释,re:仅描述实现的函数与描述存储的状态之间的区别,似乎是对这个问题的更好回答,因为差异不仅仅是哲学上的。
      • 邓肯·马拉肖克,不是真的。豪尔赫的答案是更好的答案。 Alex 的回答侧重于力学,而 Jorge 则更多地关注语义。
      • 我喜欢你指定答案之前的陈述:Use abstract classes and inheritance if you can make the statement “A is a B”. Use interfaces if you can make the statement “A is capable of [doing] as”
      【解决方案9】:

      就我个人而言,我几乎不需要编写抽象类。

      大多数时候我看到抽象类被(误用)了,这是因为抽象类的作者使用的是“模板方法”模式。

      “模板方法”的问题在于它几乎总是在某种程度上是可重入的——“派生”类不仅知道它正在实现的基类的“抽象”方法,还知道它的公共方法基类,尽管大多数时候它不需要调用它们。

      (过于简化)示例:

      abstract class QuickSorter
      {
          public void Sort(object[] items)
          {
              // implementation code that somewhere along the way calls:
              bool less = compare(x,y);
              // ... more implementation code
          }
          abstract bool compare(object lhs, object rhs);
      }
      

      所以在这里,这个类的作者编写了一个通用算法,并打算通过提供他们自己的“钩子”来“专门化”它,让人们使用它——在这种情况下,是一个“比较”方法。

      所以预期的用法是这样的:

      class NameSorter : QuickSorter
      {
          public bool compare(object lhs, object rhs)
          {
              // etc.
          }
      }
      

      问题在于您将两个概念过度耦合在一起:

      1. 比较两个项目的一种方式(应该先去哪个项目)
      2. 一种对项目进行排序的方法(即快速排序与合并排序等)

      在上面的代码中,理论上,“比较”方法的作者可以重新进入回调超类的“排序”方法......即使在实践中他们永远不会想要或需要这样做。

      您为这种不必要的耦合付出的代价是很难更改超类,而且在大多数 OO 语言中,无法在运行时更改它。

      另一种方法是改用“策略”设计模式:

      interface IComparator
      {
          bool compare(object lhs, object rhs);
      }
      
      class QuickSorter
      {
          private readonly IComparator comparator;
          public QuickSorter(IComparator comparator)
          {
              this.comparator = comparator;
          }
      
          public void Sort(object[] items)
          {
              // usual code but call comparator.Compare();
          }
      }
      
      class NameComparator : IComparator
      {
          bool compare(object lhs, object rhs)
          {
              // same code as before;
          }
      }
      

      现在请注意:我们所拥有的只是接口,以及这些接口的具体实现。实际上,您真的不需要任何其他东西来进行高级 OO 设计。

      为了“隐藏”我们已经通过使用“QuickSort”类和“NameComparator”实现“名称排序”这一事实,我们可能仍会在某处编写工厂方法:

      ISorter CreateNameSorter()
      {
          return new QuickSorter(new NameComparator());
      }
      

      任何时候只要你有一个抽象类,你就可以这样做......即使在基类和派生类之间存在自然的可重入关系时,将它们显式化通常也是值得的。

      最后一个想法:我们上面所做的只是通过使用“QuickSort”函数和“NameComparison”函数来“组合”一个“NameSorting”函数......在函数式编程语言中,这种编程风格变得更加均匀更自然,代码更少。

      【讨论】:

      • 仅仅因为你可以使用抽象类或模板方法模式并不意味着你需要避免它们。策略模式是针对本示例中不同情况的不同模式,但有很多示例表明模板模式比策略更适合。
      • 嗯,根据我的经验,我从来没有遇到过它们(模板方法更可取的情况)......或者很少遇到。这就是“抽象”的全部 - 对“模板方法”设计模式的语言支持。
      • 好的,我曾将它用于一个专家系统,其过程类似于,得到 1. FillTheParameters,2. 在它们之间生成向量积,3. 对于每个 Pair 计算结果,4. 加入结果,其中第 1 步和第 3 步是委托的,第 2 步和第 4 步是在基类中实现的。
      • 我发现几乎所有抽象类的使用都更难理解。考虑相互通信而不是继承关系的盒子更容易(对我来说)......但我也同意当前的 OO 语言强制使用太多样板......函数式将是超越 OO 的方式
      • 误用的例子很简单。它很少归结为像比较这样好的剥离功能。更常见的是派生类有一些默认功能 replaceextend 的情况(在后一种情况下,调用基类函数是完全有效的)。在您的示例中没有默认功能,因此抽象类的使用没有理由。
      【解决方案10】:

      如果您心中有清晰的概念,那么什么时候做一件非常简单的事情。

      抽象类可以派生,而接口可以实现。两者之间有一些区别。当你派生一个抽象类时,派生类和基类之间的关系是“是”关系。例如,狗是动物,羊是动物,这意味着派生类从基类继承了一些属性。

      而对于接口的实现,关系是“可以”。例如,狗可以是间谍狗。狗可以是马戏团的狗。狗可以是赛狗。这意味着您实施某些方法来获取某些东西。

      我希望我很清楚。

      【讨论】:

      • 您的第二个示例仍然可以是“Is A”关系。赛狗就是狗
      【解决方案11】:

      接口比抽象类表现更好的一个有趣的地方是当您需要向一组(相关或不相关)对象添加额外功能时。如果你不能给它们一个基本的抽象类(例如,它们是sealed 或已经有一个父类),你可以给它们一个虚拟(空)接口,然后简单地为该接口编写扩展方法。

      【讨论】:

        【解决方案12】:

        我写了一篇关于何时使用抽象类以及何时使用接口的文章。除了“一个 IS-A ... 和一个 CAN-DO ...”之外,它们之间还有很多区别。对我来说,这些都是固定答案。我提到了一些何时使用它们中的任何一个的原因。希望对您有所帮助。

        http://codeofdoom.com/wordpress/2009/02/12/learn-this-when-to-use-an-abstract-class-and-an-interface/

        【讨论】:

          【解决方案13】:

          好的,我自己刚刚“摸索”了这个 - 这是外行的术语(如果我错了,请随时纠正我) - 我知道这个话题太老了,但有一天其他人可能会偶然发现它......

          抽象类允许您创建蓝图,并允许您另外构建(实现)您希望其所有后代拥有的属性和方法。

          另一方面,接口只允许您声明您希望具有给定名称的属性和/或方法存在于所有实现它的类中 - 但不指定您应该如何实现它。此外,一个类可以实现许多接口,但只能扩展一个抽象类。接口更像是一种高级架构工具(如果您开始掌握设计模式,它会变得更加清晰) - 抽象在两个阵营中都有立足点,并且也可以执行一些肮脏的工作。

          为什么要使用一个而不是另一个?前者允许对后代进行更具体的定义——后者允许更大的多态性。最后一点对最终用户/编码人员很重要,他们可以利用此信息以各种组合/形状实现 A.P.I(ninterface) 以满足他们的需求。

          我认为这对我来说是“灯泡”时刻 - 少从作者的角度考虑接口,而更多地从链中的任何编码员的角度考虑接口,他们正在向项目添加实现或扩展 em> 一个 API。

          【讨论】:

          • 在此基础上构建:实现接口的对象采用它的 TYPE。这是至关重要的。因此,您可以将接口的不同变体传递给类,但使用接口的类型名称来引用它们(及其方法)。因此,您无需使用 switch 或 if/else 循环。试试这个主题的教程——它通过策略模式演示了接口的使用。 phpfreaks.com/tutorial/design-patterns---strategy-and-bridge/…
          • 我完全同意你的灯泡时刻:“A.P.I(接口)以各种组合/形状满足他们的需求”!非常非常好的观点。
          【解决方案14】:

          我的两分钱:

          接口基本上定义了一个契约,任何实现类都必须遵守(实现接口成员)。它不包含任何代码。

          另一方面,抽象类可以包含代码,并且可能有一些方法标记为抽象,继承类必须实现。

          我使用抽象类的罕见情况是,当我有一些默认功能时,继承类可能不会对覆盖某些特殊类从其继承的抽象基类(比如抽象基类)感兴趣。

          示例(一个非常初级的!):考虑一个名为 Customer 的基类,它具有像 CalculatePayment()CalculateRewardPoints() 这样的抽象方法和像 GetName()SavePaymentDetails() 这样的一些非抽象方法。

          RegularCustomerGoldCustomer 这样的特殊类将继承Customer 基类并实现它们自己的CalculatePayment()CalculateRewardPoints() 方法逻辑,但重复使用GetName()SavePaymentDetails() 方法。

          您可以向抽象类(即非抽象方法)添加更多功能,而不会影响使用旧版本的子类。而向接口添加方法会影响所有实现它的类,因为它们现在需要实现新添加的接口成员。

          具有所有抽象成员的抽象类类似于接口。

          【讨论】:

          • +1 表示“您可以向抽象类(即非抽象方法)添加更多功能,而不会影响使用旧版本的子类。而向接口添加方法会影响所有实现的类因为他们现在需要实现新添加的接口成员。”
          • 接口可以有“默认”方法,所以在接口中没有方法实现是错误的想法。 “亲子” IS-A 关系是这里的关键。此外,“共享属性”与“共享属性”。例如狗是一种动物。但是狗也可以“走路”
          【解决方案15】:

          1.如果您正在创建为不相关的类提供通用功能的东西,请使用接口。

          2.如果您要为层次结构中密切相关的对象创建一些东西,请使用抽象类。

          【讨论】:

            【解决方案16】:

            基本规则是:“名词”使用抽象类“动词”使用接口

            例如:car 是一个抽象类,drive,我们可以把它做成一个接口。

            【讨论】:

            • 这个没有意义,我们也可以把drive的功能放在车里——那是一个抽象类。
            【解决方案17】:

            类只能从一个基类继承,因此如果您想使用抽象类为一组类提供多态性,它们必须都继承自该类。抽象类也可以提供已经实现的成员。因此,您可以确保与抽象类具有一定数量的相同功能,但不能与接口相同。

            这里有一些建议可以帮助您决定是使用接口还是抽象类来为组件提供多态性。

            • 如果您预期创建组件的多个版本,请创建一个抽象类。抽象类提供了一种简单易行的方式来对组件进行版本控制。通过更新基类,所有继承类都会随着更改而自动更新。另一方面,接口一旦以这种方式创建就无法更改。如果需要新版本的接口,则必须创建一个全新的接口。
            • 如果您正在创建的功能将适用于各种不同的对象,请使用接口。抽象类应该主要用于密切相关的对象,而接口最适合为不相关的类提供通用功能。
            • 如果您正在设计小而简洁的功能,请使用接口。如果您正在设计大型功能单元,请使用抽象类。
            • 如果要在组件的所有实现中提供通用的已实现功能,请使用抽象类。抽象类允许您部分实现您的类,而接口不包含任何成员的实现。

            复制自:
            http://msdn.microsoft.com/en-us/library/scsyfw1d%28v=vs.71%29.aspx

            【讨论】:

            • UML 中没有任何东西可以排除多类继承。多重继承是由编程语言决定的,而不是由 UML 决定的。例如,Java 和 C# 中不允许多类继承,但 C++ 中允许。
            • @BobRodes:面向对象的框架可以以各种组合提供许多特性,但不是所有组合。广义多重继承排除了一些其他有用的特性组合,包括将引用直接转换为实际实例的任何父类型或任何由此支持的接口类型的能力,以及独立编译基类型和派生类型并在运行时加入它们的能力。
            • @supercat 你的文章很好地解释了使用多重继承导致的一些问题。然而,UML 中没有任何东西可以排除图表中的多类继承。我在回应上面的“类可能只继承一个基类......”,但事实并非如此。
            • @BobRodes:这个问题被标记为 Java。 Java 包含指定的功能,因此仅限于不能产生“致命钻石”的多重继承形式(尽管事实上他们实现默认接口实现的方式使得致命钻石成为可能)。
            • @supercat 哦,好的。我通常不看 java 标签,所以在我写的时候,我至少认为我在评论一个 UML 答案。无论如何,我同意你的评论。
            【解决方案18】:

            如果您将 java 视为 OOP 语言,

            接口不提供方法实现”不再适用于 Java 8 启动。现在java在接口中提供了默认方法的实现。

            简单来说,我想用

            接口:通过多个不相关的对象来实现一个契约。它提供了“HAS A”能力。

            抽象类:在多个相关对象之间实现相同或不同的行为。它建立了“IS A”关系。

            Oracle website 提供了 interfaceabstract 类之间的主要区别。

            考虑使用抽象类如果:

            1. 您希望在几个密切相关的类之间共享代码。
            2. 您希望扩展抽象类的类具有许多公共方法或字段,或者需要公共以外的访问修饰符(例如受保护和私有)。
            3. 您要声明非静态或非最终字段。

            考虑使用接口如果:

            1. 您希望不相关的类会实现您的接口。比如很多不相关的对象可以实现Serializable接口。
            2. 您想指定特定数据类型的行为,但不关心谁实现了它的行为。
            3. 您想利用类型的多重继承。

            例子:

            抽象类(IS A关系)

            Reader 是一个抽象类。

            BufferedReaderReader

            FileReaderReader

            FileReaderBufferedReader 用于共同目的:读取数据,它们通过Reader 类关联。

            接口(具有能力)

            Serializable 是一个接口。

            假设您的应用程序中有两个类,它们正在实现Serializable 接口

            Employee implements Serializable

            Game implements Serializable

            这里你不能通过Serializable接口在EmployeeGame之间建立任何关系,这意味着不同的目的。两者都能够序列化状态并且比较到此结束。

            看看这些帖子:

            How should I have explained the difference between an Interface and an Abstract class?

            【讨论】:

            • 我认为的最佳答案。
            • 每个人都能通过示例学得更好。很好的答案。谢谢!
            【解决方案19】:

            对我来说,在很多情况下我会选择接口。但在某些情况下我更喜欢抽象类。

            OO 中的类通常是指实现。当我想将一些实现细节强加给孩子时,我会使用抽象类,否则我会使用接口。

            当然,抽象类不仅在强制实现方面很有用,而且在许多相关类之间共享某些特定细节方面也很有用。

            【讨论】:

              【解决方案20】:

              如果以下任何陈述适用于您的情况,请考虑使用抽象类

              1. 您希望在几个密切相关的类之间共享代码。
              2. 您希望扩展抽象类的类具有许多公共方法或字段,或者需要公共以外的访问修饰符(例如受保护和私有)。
              3. 您要声明非静态或非最终字段。这使您能够定义可以访问和修改它们所属对象状态的方法。

              如果以下任何陈述适用于您的情况,请考虑使用接口

              1. 您希望不相关的类会实现您的接口。例如,接口 Comparable 和 Cloneable 由许多不相关的类实现。
              2. 您想指定特定数据类型的行为,但不关心谁实现其行为。
              3. 您想利用多重继承。

              Source

              【讨论】:

                【解决方案21】:

                我认为最简洁的表达方式如下:

                共享属性 => 抽象类。
                共享功能 => 接口。

                而且说得不那么简洁......

                抽象类示例:

                public abstract class BaseAnimal
                {
                    public int NumberOfLegs { get; set; }
                
                    protected BaseAnimal(int numberOfLegs)
                    {
                        NumberOfLegs = numberOfLegs;
                    }
                }
                
                public class Dog : BaseAnimal
                {
                    public Dog() : base(4) { }
                }
                
                public class Human : BaseAnimal 
                {
                    public Human() : base(2) { }
                }
                

                由于动物有一个共享属性——在这种情况下是腿数——所以创建一个包含这个共享属性的抽象类是有意义的。这也允许我们编写对该属性进行操作的通用代码。例如:

                public static int CountAllLegs(List<BaseAnimal> animals)
                {
                    int legCount = 0;
                    foreach (BaseAnimal animal in animals)
                    {
                        legCount += animal.NumberOfLegs;
                    }
                    return legCount;
                }
                

                接口示例:

                public interface IMakeSound
                {
                    void MakeSound();
                }
                
                public class Car : IMakeSound
                {
                    public void MakeSound() => Console.WriteLine("Vroom!");
                }
                
                public class Vuvuzela : IMakeSound
                {
                    public void MakeSound() => Console.WriteLine("VZZZZZZZZZZZZZ!");        
                }
                

                请注意,Vuvuzelas 和 Cars 是完全不同的东西,但它们有共同的功能:发出声音。因此,接口在这里是有意义的。此外,它将允许程序员将发出声音的事物组合在一个通用接口下——在本例中为IMakeSound。通过这种设计,您可以编写以下代码:

                List<IMakeSound> soundMakers = new List<ImakeSound>();
                soundMakers.Add(new Car());
                soundMakers.Add(new Vuvuzela());
                soundMakers.Add(new Car());
                soundMakers.Add(new Vuvuzela());
                soundMakers.Add(new Vuvuzela());
                
                foreach (IMakeSound soundMaker in soundMakers)
                {
                    soundMaker.MakeSound();
                }
                

                你能说出那会输出什么吗?

                最后,您可以将两者结合起来。

                组合示例:

                public interface IMakeSound
                {
                    void MakeSound();
                }
                
                public abstract class BaseAnimal : IMakeSound
                {
                    public int NumberOfLegs { get; set; }
                
                    protected BaseAnimal(int numberOfLegs)
                    {
                        NumberOfLegs = numberOfLegs;
                    }
                
                    public abstract void MakeSound();
                }
                
                public class Cat : BaseAnimal
                {
                    public Cat() : base(4) { }
                
                    public override void MakeSound() => Console.WriteLine("Meow!");
                }
                
                public class Human : BaseAnimal 
                {
                    public Human() : base(2) { }
                
                    public override void MakeSound() => Console.WriteLine("Hello, world!");
                }
                

                在这里,我们要求所有BaseAnimals 发出声音,但我们还不知道它的实现。在这种情况下,我们可以抽象接口实现并将其实现委托给它的子类。

                最后一点,还记得在抽象类示例中我们如何能够对不同对象的共享属性进行操作,而在接口示例中我们如何能够调用不同对象的共享功能?在最后一个例子中,我们可以两者都做。

                【讨论】:

                  【解决方案22】:

                  何时更喜欢抽象类而不是接口?

                  1. 如果计划在程序/项目的整个生命周期内更新基类,最好让基类成为抽象类
                  2. 如果尝试为层次结构中密切相关的对象构建主干,则使用抽象类非常有益

                  何时更喜欢接口而不是抽象类?

                  1. 如果不处理大量分层类型的框架,接口将是一个不错的选择
                  2. 由于抽象类不支持多重继承(菱形问题),接口可以节省时间

                  【讨论】:

                  • 同样的想法让我寻找一个简单的问题答案。
                  • FWIW,我真的很喜欢这个答案。
                  【解决方案23】:

                  简短的回答:abstract 类允许您创建子类可以实现或覆盖的功能。 接口 只允许您定义功能,而不是实现它。虽然一个类只能扩展一个抽象类,但它可以利用多个接口。

                  【讨论】:

                    【解决方案24】:

                    如果我们有一个对所有派生类都相同的实现,那么最好在接口上使用抽象类。当我们有一个接口时,我们可以将我们的实现移动到任何实现接口的类。在抽象类中,它避免了代码重复并共享所有派生类的实现。这些接口允许开发有助于更好测试的松散耦合系统。

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 2020-02-25
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2022-10-24
                      • 1970-01-01
                      • 2013-09-21
                      相关资源
                      最近更新 更多