488

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

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

4

24 回答 24

492

我为此写了一篇文章:

抽象类和接口

总结:

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

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

于 2009-01-26T09:01:27.080 回答
480

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

于 2009-01-26T08:52:47.923 回答
84

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

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

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

(过度简化)示例:

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”函数......在函数式编程语言中,这种编程风格变得更加自然,用更少的代码。

于 2009-01-26T10:12:37.060 回答
64

如果您将 java 视为 OOP 语言,

接口不提供方法实现”在 Java 8 启动时不再有效。现在java在接口中为默认方法提供了实现。

简单来说,我想使用

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

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

Oracle网站interface提供了与abstract类之间的关键区别。

在以下情况下考虑使用抽象类

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

在以下情况下考虑使用接口

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

例子:

抽象类(IS A关系)

Reader是一个抽象类。

BufferedReader是一个Reader

FileReader是一个Reader

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

接口(能力)

可序列化是一个接口。

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

Employee implements Serializable

Game implements Serializable

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

看看这些帖子:

我应该如何解释接口和抽象类之间的区别?

于 2015-11-27T19:04:48.497 回答
45

我的两分钱:

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

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

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

示例(一个非常基本的示例!):考虑一个名为 Customer 的基类,它具有抽象方法,如,CalculatePayment()以及CalculateRewardPoints()一些非抽象方法GetName(),如SavePaymentDetails()

专门的类喜欢RegularCustomer andGoldCustomer将从Customer基类继承并实现自己的CalculatePayment()andCalculateRewardPoints()方法逻辑,但重用GetName()andSavePaymentDetails()方法。

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

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

于 2010-12-07T18:11:17.563 回答
40

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

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

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

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

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

于 2009-07-12T11:49:24.917 回答
31

什么时候做一件非常简单的事情,如果你心里清楚这个概念。

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

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

我希望我很清楚。

于 2009-01-26T10:41:12.790 回答
13

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

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

于 2013-03-28T16:13:14.383 回答
12

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

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

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

  1. 如果不处理大量分层类型的框架,接口将是一个不错的选择
  2. 因为抽象类不支持多重继承(钻石问题),接口可以节省一天
于 2018-10-16T00:19:53.297 回答
10

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

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

于 2009-02-12T19:19:33.280 回答
9

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

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

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

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

于 2014-12-13T08:36:08.410 回答
7

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

共享属性 => 抽象类。
共享功能 => 界面。

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

抽象类示例:

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!");
}

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

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

于 2017-06-13T22:58:12.060 回答
4

如果这些陈述中的任何一个适用于您的情况,请考虑使用抽象类:

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

如果这些陈述中的任何一个适用于您的情况,请考虑使用接口:

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

来源

于 2017-04-21T07:11:02.040 回答
2

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

于 2009-01-26T08:48:46.293 回答
2

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

于 2009-01-26T08:55:12.333 回答
2

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

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

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

于 2016-06-03T02:41:43.737 回答
1

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

于 2009-01-26T08:52:18.737 回答
1

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

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

于 2009-01-26T08:53:30.713 回答
1

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

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

于 2009-01-26T08:56:55.140 回答
1

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

于 2009-01-26T11:00:47.323 回答
1

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

于 2020-08-10T13:04:52.073 回答
0

抽象类可以有实现。

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

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

于 2009-01-26T08:54:14.503 回答
0

基本的经验法则是:“名词”使用抽象类,“动词”使用接口

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

于 2014-05-09T19:08:52.350 回答
0

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

于 2021-11-12T11:04:47.607 回答