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

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

什么时候想要使用一个接口,什么时候想要使用一个抽象类

我写了一篇关于这个的文章:

抽象类和接口

总结:

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

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

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

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

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

“模板方法”的问题在于它几乎总是有些重入 – “派生”类不仅知道它正在实现的基类的“抽象”方法,而且还知道基类的公共方法即使大部分时间不需要打电话给他们。

(过分简化)例子:

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

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

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

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

这个问题是你不适当地把两个概念联系在一起:

  1. 比较两个项目的方法(哪个项目应该先去)
  2. 一个sorting项目的方法(即快速sorting与合并sorting等)

在上面的代码中,理论上,“比较”方法的作者可以重复地回到超类“sorting”方法中,尽pipe在实践中他们将永远不想或不需要这样做。

你为这种不需要的耦合付出的代价是很难改变超类,在大多数的OO语言中,在运行时不可能改变它。

另一种方法是使用“策略”devise模式:

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

所以现在注意:我们所有的都是接口,以及这些接口的具体实现。 在实践中,你并不需要做任何事情来做一个高层面的OOdevise。

为了“隐藏”我们已经通过使用“QuickSort”类和“NameComparator”来实现“sorting名称”的事实,我们仍然可以在某处编写一个工厂方法:

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

每当你有一个抽象类,你都可以做到这一点……即使在基类和派生类之间存在一个自然的可重入关系时,通常也要把它们明确地表示出来。

最后一个想法:我们上面所做的是通过使用“QuickSort”函数和“NameComparison”函数“编写”NameSorting函数…在function性编程语言中,这种编程风格变得更加自然,用较less的代码。

好吧,只要“亲自”就可以了 – 这里是通俗的说法(如果我错了,可以随时纠正我) – 我知道这个话题是有用的,但有一天有人会偶然发现这个话题。

抽象类允许你创build一个蓝图,并允许你另外构build(实现)你想要的所有后代拥有的属性和方法。

另一方面,一个接口只允许你声明你想要一个给定名称的属性和/或方法存在于所有实现它的类中 – 但是并不指定你应该如何实现它。 另外,一个类可以实现多个接口,但只能扩展一个Abstract类。 一个接口更像是一个高层次的build筑工具(如果你开始掌握devise模式,这个工具就会变得更加清晰) – 一个Abstract在这两个阵营中都有一席之地,可以完成一些肮脏的工作。

为什么使用一个呢? 前者允许更加具体的后代定义 – 后者允许更多的多态性 。 最后一点对最终用户/编码员来说很重要,他们可以利用这些信息来实现各种组合/形状的AP I(接口)以满足他们的需求。

我认为这对我来说是“灯泡”时刻 – 从作者的angular度来考虑接口,而不是来自任何编码人员,这些编码人员将在实现项目中增加实现或扩展 API。

我的两分钱:

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

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

我使用抽象类的情况很less见,当我有一些默认的function时,inheritance类可能在重写,比如说一个抽象基类中,有些特定的类inheritance。

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

像RegularCustomer和GoldCustomer这样的专门化的类将inheritance自Customer基类并实现自己的CalculatePayment()和CalculateRewardPoints()方法逻辑,但是重新使用GetName()和SavePaymentDetails()方法。

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

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

如果你有清楚的概念,什么时候做一件很简单的事情。

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

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

我希望我很清楚。

如果您将java看作OOP语言,

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

简单来说,我想用

接口:通过多个不相关的对象来实现合同。 它提供“ HAS A ”function。

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

Oracle 网站提供了interfaceabstract类之间的关键区别。

考虑使用抽象类如果:

  1. 你想在几个密切相关的类中分享代码。
  2. 您期望扩展抽象类的类具有许多常用的方法或字段,或者需要公共访问修饰符(例如protected和private)。
  3. 您想要声明非静态或非最终字段。

考虑使用接口如果:

  1. 你期望不相关的类将实现你的接口。 例如,许多不相关的对象可以实现Serializable接口。
  2. 您想要指定特定数据types的行为,但不关心谁实现其行为。
  3. 你想利用types的多重inheritance。

例:

抽象类( IS关系)

读者是一个抽象的类。

BufferedReader是一个Reader

FileReader是一个Reader

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

接口( HAS Afunction)

可串行化是一个接口。

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

Employee implements Serializable

Game implements Serializable

这里你不能通过EmployeeGame之间的Serializable接口build立任何关系,这是为了不同的目的。 两者都能够对状态进行序列化,并在那里结束比较。

看看这些post:

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

我写了一篇关于何时使用抽象类以及何时使用接口的文章。 除了“一个IS-A …和一个CAN-DO …”以外,还有更多不同之处。 对我来说,这些是jar装答案。 我提到几个什么时候使用它们的原因。 希望它有帮助。

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

1.如果你正在创build一些为不相关类提供通用function的东西,可以使用一个接口。

2.如果要为层次结构中密切相关的对象创build某个对象,请使用抽象类。

类只能从一个基类inheritance,所以如果你想使用抽象类为一组类提供多态性,它们都必须从这个类inheritance。 抽象类也可以提供已经实现的成员。 因此,可以确保抽象类具有一定的相同function,但不能使用接口。

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

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

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

如果这些语句适用于您的情况,请考虑使用抽象类

  1. 你想在几个密切相关的类中分享代码。
  2. 你期望扩展你的抽象类的类有很多通用的方法或者字段,或者需要访问修饰符而不是public(比如protected和private)。
  3. 您想要声明非静态或非最终字段。 这使您可以定义可访问和修改其所属对象状态的方法。

如果以下任何一种语句适用于您的情况,请考虑使用接口

  1. 你期望不相关的类将实现你的接口。 例如,Comparable和Cloneable接口是由许多不相关的类实现的。
  2. 您想要指定特定数据types的行为,但不关心谁实现其行为。
  3. 你想利用多重inheritance。

资源

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

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

纯粹基于inheritance,在明确定义后代,抽象关系(即动物 – >猫)和/或要求inheritance虚拟或非公有属性的情况下,尤其是共享状态(接口无法支持)。

你应该尝试和赞成合成(通过dependency injection),而不是inheritance,尽pipe如此,注意接口是契约支持unit testing,分离关注和(语言变化)多重inheritance。

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

我认为最简单的方法是:

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

并且简洁地说…

抽象类例子:

 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是完全不同的东西,但它们具有共同的function:发出声音。 因此,这里的界面是有意义的。 此外,它将允许程序员将在一个通用界面下发出声音的东西分组 – 在这种情况下是IMakeSound 。 有了这个devise,你可以写下面的代码:

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

最后一点,请记住我们如何在抽象类的例子中对不同对象的共享属性进行操作,并且在接口的例子中我们能够调用不同对象的共享function? 在这最后一个例子中,我们可以做到这一点。

在Java中,你可以inheritance一个(抽象)类来“提供”function,你可以实现许多接口来“确保”function

这可能是一个非常困难的电话,使…

我可以给出一个指针:一个对象可以实现多个接口,而一个对象只能inheritance一个基类(像C#这样的现代OO语言,我知道C ++有多重inheritance – 但不是那么皱眉?)

抽象类可以有实现。

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

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

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

面向对象的类通常指的是实现。 当我想强制一些实现细节的孩子,我使用抽象类,否则我去接口。

当然,抽象类不仅在强制执行方面有用,而且在许多相关类中也有一些具体的细节。

基本的拇指规则是:对于“名词”使用抽象类和“动词”使用接口

例如: car是一个抽象的类和drive ,我们可以把它作为一个接口。