ChatGPT解决这个技术问题 Extra ChatGPT

何时使用接口而不是抽象类,反之亦然?

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

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

除了下面的答案之外,这是一个很好的简短列表,其中列出了您可能希望在哪些地方更喜欢接口以及在哪些地方不喜欢:何时使用接口:msdn.microsoft.com/en-us/library/3b5b8ezk(v=vs.80).aspx
当您不确定课程要做什么时,请使用抽象。如果你是,请使用界面。
我很想看看有多少不在微软工作的开发人员在他们的日常开发中定义和使用接口。

J
Jorge Córdoba

我为此写了一篇文章:

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 的回答侧重于力学,而 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”
很好地解释了:-)
A
Alex

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


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

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

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

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

(过度简化)示例:

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.
    }
}

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

比较两个项目的方法(应该先去哪个项目) 排序项目的方法(即快速排序与合并排序等)

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

您为这种不必要的耦合付出的代价是很难更改超类,而且在大多数 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 的方式
滥用的例子是非常微不足道的。它很少归结为像比较这样好的剥离功能。更常见的是派生类替换或扩展某些默认功能的情况(在后一种情况下,调用基类函数是完全有效的)。在您的示例中,没有默认功能,因此抽象类的使用没有理由。
G
Graham

如果您将 java 视为 OOP 语言,

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

简单来说,我想使用

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

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

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

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

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

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

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

例子:

抽象类(IS A 关系)

Reader 是一个抽象类。

BufferedReader 是一个 Reader

FileReader 是一个 Reader

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?


我认为最好的答案。
每个人都可以通过示例更好地学习。很好的答案。谢谢!
D
Dherik

我的两分钱:

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

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

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

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

RegularCustomerGoldCustomer 等专用类将从 Customer 基类继承并实现自己的 CalculatePayment()CalculateRewardPoints() 方法逻辑,但会重复使用 GetName()SavePaymentDetails() 方法。

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

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


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

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

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

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

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

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


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

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

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

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

我希望我很清楚。


您的第二个示例仍然可以是“Is A”关系。赛狗是狗
A
Andrew Barber

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

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


S
Satya

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

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

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

如果不处理大量分层类型的框架,接口将是一个不错的选择因为抽象类不支持多重继承(钻石问题),接口可以节省时间


相同类型的思维使我寻找问题的简单答案。
FWIW,我真的很喜欢这个答案。
佚名

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

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


M
Mazhar

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

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

如果您预期创建组件的多个版本,请创建一个抽象类。抽象类提供了一种简单易行的方式来对组件进行版本控制。通过更新基类,所有继承类都会随着更改而自动更新。另一方面,接口一旦以这种方式创建就无法更改。如果需要新版本的接口,则必须创建一个全新的接口。

如果您正在创建的功能对各种不同的对象都很有用,请使用接口。抽象类应该主要用于密切相关的对象,而接口最适合为不相关的类提供通用功能。

如果您正在设计小而简洁的功能,请使用接口。如果您正在设计大型功能单元,请使用抽象类。

如果您想在组件的所有实现中提供通用的、已实现的功能,请使用抽象类。抽象类允许您部分实现您的类,而接口不包含任何成员的实现。

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


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

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

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

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

抽象类示例:

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 发出声音,但我们还不知道它的实现。在这种情况下,我们可以抽象接口实现并将其实现委托给它的子类。

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


A
Abhijeet Ashok Muneshwar

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

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

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

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

Source


S
Sebastian

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


谢谢塞巴斯蒂安。但是如果我不需要一个基本的实现呢?如果这是它们之间的唯一区别,那么抽象类和接口是否相同?为什么有区别?
因为有些语言没有接口——C++。
N
Nick Fortescue

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


R
Ravindra babu

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

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

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


P
Peter Miehle

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


lil'提示:如果你想继承一个抽象类和一个接口,请确保抽象类实现了接口
M
Mesh

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

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


多重继承允许 Mixin 的无缝实现,编写良好的 Mixin 是轻而易举的工作,但很难获得,而且很难在不达标的情况下编写。尽管 IMO,Mixin 整体上还是很酷的。
实际上我没有,多重继承确实是我们极客之间争论的焦点,我认为绝对没有理由拒绝投票。事实上,我赞成你的回答。
我试图提出的唯一一点是,在具有单一继承的语言中混入的方法也是可能的(C#、PHP、javascript),但通过hacky 行为或俗气的语法。当他们工作时,我喜欢 Mixin,但我仍然不确定是否要使用多重继承。
这个答案更多的是语法差异而不是设计差异。我认为他是在要求设计差异
a
annakata

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

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


D
Dmitri Nesteruk

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


A
Ayoub Boumzebra

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


G
Gerrie Schenck

抽象类可以有实现。

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

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


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

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

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


这没有意义,我们也可以把drive的功能放在车里——那是一个抽象类。
s
satheesh

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


M
Mohammadreza

两者都是类定义的合同:

结论 1:两者的意图都是对象泛化

在定义抽象类时,它们也可以有默认实现。

结论2:区分在于行为泛化设计

在使用抽象类时,类只能从一个抽象类继承

结论3:抽象类在使用上存在局限性。这意味着行为泛化的限制。

最终结论 - 何时使用 which:区分是在行为概括级别

在设计类的行为时,如果功能只是概念上限制在确定的类之间,或者换句话说,是在确定的类之间共享,则使用抽象类。但是如果功能比确定的类更通用,或者我们可以/想要向其他类添加功能,请使用接口作为合同。