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

这可能是一个通用的OOP问题。我想在接口和抽象类的使用基础上做一个通用的比较。

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

395351 次浏览

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

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

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

这是一个很难打的电话。

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

抽象类可以有实现。

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

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

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

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

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

我为此写了一篇文章:

抽象类和接口

总结:

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

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

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

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

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

(过于简化的)例子:

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. 排序项目的方法(即快速排序vs归并排序等)

在上面的代码中,理论上,"compare"方法的作者可以凹角回调到超类"Sort"方法…即使在实践中,他们永远不会想要或需要这样做。

为这种不必要的耦合付出的代价是,很难更改超类,而且在大多数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”函数……在函数式编程语言中,这种编程风格变得更加自然,代码更少。

什么时候做什么是一件非常简单的事情,如果你有清晰的概念在你的脑海里。

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

而对于接口的实现,关系是“可以是”。例:狗可以是间谍犬。狗可以是马戏团的狗。狗可以是比赛犬。这意味着你要实现特定的方法来获取某些东西。

我希望我讲清楚了。

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

我写过一篇关于何时使用抽象类和何时使用接口的文章。除了“一个是a……”之外,他们之间还有很多不同之处。一个能做……”。对我来说,这些都是事先准备好的答案。我提到了使用它们中的任何一种的一些原因。希望能有所帮助。

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

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

抽象类允许你创建一个蓝图,并允许你额外构造(实现)属性和方法,你希望它的所有后代都拥有。

另一方面,接口只允许您声明希望在实现它的所有类中存在具有给定名称的属性和/或方法——但不指定应该如何实现它。同样,一个类可以实现许多接口,但只能扩展一个抽象类。界面更像是一种高级架构工具(如果你开始掌握设计模式,这一点就会变得更清楚)——抽象则同时涉足这两个阵营,也可以执行一些繁琐的工作。

为什么使用其中一种而不是另一种呢?前者允许更多的混凝土定义后代-后者允许更大的多态性。最后一点对最终用户/编码器很重要,他们可以利用这些信息以各种组合/形状来实现A.P.我(nterface),以满足他们的需求。

我认为这对我来说是一个“电灯泡”时刻——少从作者的角度考虑接口,多从后面加入到项目实现链的编码器的角度考虑接口,或者扩展一个API。

我的观点是:

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

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

我很少使用抽象类的情况是,当我有一些默认功能时,继承类可能对重写不感兴趣,比如一个抽象基类,一些专门的类继承自它。

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

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

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

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

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

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

基本的经验法则是:对于“名词”使用“抽象”类和“动词”使用界面

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

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

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

  • 如果您希望创建组件的多个版本,请创建一个抽象类。抽象类提供了一种简单的方法来控制组件的版本。通过更新基类,所有继承的类都会随着更改而自动更新。另一方面,接口一旦以这种方式创建就不能更改。如果需要一个接口的新版本,则必须创建一个全新的接口。 如果您正在创建的功能将对广泛的不同对象有用,则使用接口。抽象类应该主要用于密切相关的对象,而接口最适合为不相关的类提供公共功能。 李李< / > < > 如果您正在设计小而简洁的功能,请使用接口。如果您正在设计大型功能单元,请使用抽象类。 < /李>
  • 如果您希望在组件的所有实现中提供通用的实现功能,请使用抽象类。抽象类允许部分实现类,而接口不包含任何成员的实现。
  • < / ul > < / p > < p >复制:
    http://msdn.microsoft.com/en-us/library/scsyfw1d%28v=vs.71%29.aspx < / p >

如果你认为java是面向对象语言,

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

简单来说,我想用

< em >接口:< / em >由多个不相关的对象实现一个契约。它提供了“有一个”功能。

在多个相关对象中实现相同或不同的行为。它建立了"是一个"关系。

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

考虑使用抽象类如果:

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

考虑使用接口如果:

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

例子:

抽象类(是一个关系)

读者是一个抽象类。

BufferedReader是一个Reader

FileReader是一个Reader

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

接口(有一个能力)

可序列化的是一个接口。

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

Employee implements Serializable

Game implements Serializable

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

看看这些帖子:

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

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

OO中的类通常指的是实现。当我想强制一些实现细节到子程序时,我使用抽象类,否则我就使用接口。

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

如果下列语句适用于你的情况,请考虑使用抽象类:

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

如果下列语句适用于你的情况,请考虑使用接口:

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

Source .

我认为最简洁的说法是:

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

更简单地说……

抽象类示例:

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

这里要注意的是,呜呜祖拉和汽车是完全不同的东西,但它们有共同的功能:发出声音。因此,接口在这里是有意义的。此外,它将允许程序员将发出声音的东西分组在一个公共接口下——在本例中为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!");
}

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

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

什么时候选择抽象类而不是接口?

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

什么时候选择接口而不是抽象类?

  1. 如果不需要处理大量的层次结构类型的框架,那么接口将是一个很好的选择
  2. 因为抽象类不支持多重继承(菱形问题),所以接口可以挽救局面

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

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

它们都是合同用于类定义:

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

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

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

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

结论3:抽象类在应用上存在局限性。它的意思是 行为概化的限制

最终结论-何时使用which: Distinguish在行为泛化水平

在设计类的行为时,如果功能在概念上是只是,或者换句话说,是在确定的类之间共享,则使用抽象类。但如果function是比确定的类更一般或我们/希望可以添加功能到其他类,使用接口作为契约。

车辆包括汽车、坦克、飞机、手推车等。

抽象类Vehicle可以有子类,如car、tank、plane、cart等。

public abstract Vehicle {...}


public Car extends Vehicle {...}


public Tank extends Vehicle {...}
那么,什么是Movable?几乎一切! < / p >

石头,蛋糕,汽车,行星,星系,甚至你都是可移动的!

它们中的大多数也是可观察的!

还有,什么是可吃的?这是一款名为《美味星球》的游戏。 < / p >

石头,汽车,行星,星系,甚至时间!

public interface Movable {...}
public interface Observable {...}
public interface Eatable {...}


public class Stone implements Movable, Eatable, Observable {...}


public class Time implements Eatable, Observable {...}


public class Stupidity implements Observable {...}

终于!

public class ChocolateCar extends Vehicle implements Eatable {...}