Java中的接口和抽象类混淆与示例

我无法理解何时使用接口而不是抽象类,反之亦然。 此外,我很困惑何时扩展与另一个接口的接口。 对于长篇文章感到抱歉,但这非常令人困惑。

创建形状似乎是一个受欢迎的起点。 假设我们想要一种模拟2D形状的方法。 我们知道每个形状都有一个区域。 以下两个实现之间有什么区别:

带接口:

public interface Shape { public double area(); } public class Square implements Shape{ private int length = 5; public Square(){...} public double area() return length * length; } } 

与抽象类:

 abstract class Shape { abstract public double area(); } public class Square extends Shape { private length = 5; public Square(){...} public double area(){ return length * length; } 

我知道抽象类允许您定义实例变量并允许您提供方法实现,而接口不能执行这些操作。 但在这种情况下,似乎这两个实现是相同的。 所以使用任何一个都没问题?

但现在说我们要描述不同类型的三角形。 我们可以有等腰,锐角和直角三角形。 对我来说,在这种情况下使用类inheritance是有意义的。 使用’IS-A’定义:右三角形“IS-A”三角形。 三角形“IS-A”形状。 此外,抽象类应定义所有子类中常见的行为和属性,因此这是完美的:

与抽象类

 abstract Triangle extends Shape { private final int sides = 3; } class RightTriangle extends Triangle { private int base = 4; private int height = 5; public RightTriangle(){...} public double area() { return .5 * base * height } } 

我们也可以使用接口来实现这一点,Triangle和Shape是接口。 但是,与类inheritance不同(使用’IS-A’关系来定义什么应该是子类),我不知道如何使用接口。 我看到两种方式:

第一种方式:

  public interface Triangle { public final int sides = 3; } public class RightTriangle implements Triangle, Shape { private int base = 4; private int height = 5; public RightTriangle(){} public double area(){ return .5 * height * base; } } 

第二种方式:

 public interface Triangle extends Shape { public final int sides = 3; } public class RightTriangle implements Triangle { .... public double area(){ return .5 * height * base; } } 

在我看来,这两种方式都有效。 但是什么时候你会用另一种方式呢? 使用接口而不是抽象类来表示不同的三角形有什么好处吗? 尽管我们对形状的描述进行了复杂化,但使用接口与抽象类仍然相似。

接口的一个关键组件是它可以定义可以在不相关的类之间共享的行为。 因此Flyable接口将出现在Airplane和Bird中。 因此,在这种情况下,显然接口方法是首选。

另外,要构建扩展另一个接口的混乱接口:何时应该在决定应该是什么接口时忽略’IS-A’关系? 举个例子: LINK 。

为什么’VeryBadVampire’应该是一个类而’吸血鬼’是一个接口? 一个’VeryBadVampire’IS-A’吸血鬼’,所以我的理解是’吸血鬼’应该是一个超类(可能是抽象类)。 “吸血鬼”类可以实施“致命”来保持其致命行为。 此外,’吸血鬼’IS-A’怪物’,所以’怪物’也应该是一个类。 “吸血鬼”类也可以实现一个名为“危险”的界面来保持其危险行为。 如果我们想创造一个名为’BigRat’的新怪物,这个怪物很危险但不致命,那么我们就可以创建一个’BigRat’类来扩展’Monster’并实现’Dangerous’。

以上是否会实现与使用’Vampire’作为接口相同的输出(在链接中描述)? 我看到的唯一区别是使用类inheritance并保留’IS-A’关系会消除很多混乱。 然而,这并没有遵循。 这样做有什么好处?

即使你想要一个怪物来分享吸血鬼的行为,人们总是可以重新定义对象的表示方式。 如果我们想要一种名为’VeryMildVampire’的新型吸血鬼怪物并且我们想要创造一个名为’Chupacabra’的类似吸血鬼的怪物,我们可以这样做:

‘吸血鬼’课程延伸’怪物’实施’危险’,’致命’,’血腥’
‘VeryMildVampire’类扩展了’Vampire’类
‘Chupacabra’课程延伸’怪物’实施’BloodSuckable’

但我们也可以这样做:

‘VeryMildVampire’扩展’Monster’实现了Dangerous,Lethal,Vampiric
‘Chupacabra’扩展’Monster’实现Dangerous,Vampiric

第二种方法是创建一个’Vampiric’界面,这样我们就可以更容易地定义一个相关的怪物,而不是创建一堆定义吸血鬼行为的界面(如第一个例子中所示)。 但这打破了IS-A的关系。 所以我很困惑……

记住使用抽象类或接口时的基本概念。

当扩展的类与实现它的类更紧密地耦合时,即当两者都具有父子关系时,使用抽象类。

例如:

  abstract class Dog {} class Breed1 extends Dog {} class Breed2 extends Dog {} 

Breed1Breed2都是狗的类型,并且具有作为狗的一些共同行为。

然而,当实现类具有可以从类实现的function时,使用接口。

  interface Animal { void eat(); void noise(); } class Tiger implements Animal {} class Dog implements Animal {} 

TigerDog是两个不同的类别,但是吃和发出噪音,这是不同的。 所以他们可以使用Animal食物和噪音。

如果要使一个或多个方法不是抽象的,请使用抽象类。

如果您想保留所有摘要,请使用界面。

这是在设计有点复杂的类层次结构时会遇到的问题。 但是通常在使用抽象类和接口时需要知道的事情很少

抽象类

  • 允许您利用使用构造函数和构造函数重写的function
  • 限制具有多重inheritance的类(如果您正在设计复杂的API,这尤其有用)
  • 实例变量和方法实现
  • 利用方法超级调用的强大function(使用super调用父抽象类的实现)

接口

  • 启用多重inheritance – 您可以实现n个接口
  • 仅允许表示概念方法(无方法体)

通常使用“-able”子句的接口(如function中所示)。 例如:-

  1. Runnable
  2. Observable

使用类似is-a(进化格式)的抽象类。 例如:-

  1. Number
  2. Graphics

但是,硬性规则和快速规则并不容易创建。 希望这可以帮助

你这里有很多问题。 但我认为基本上你是在询问界面与抽象类。

使用接口,您可以拥有实现多个接口的类。 但是,如果要将其用作API,则界面不耐用。 界面发布后,很难修改界面,因为它会破坏其他人的代码。

使用抽象类,您只能扩展一个类。 但是,抽象类对于API是持久的,因为您仍然可以在以后的版本中进行修改而不会破坏其他人的代码。 同样使用抽象类,您可以预定义实现。 例如,在Triangle示例中,对于抽象类,您可能有一个方法countEdges(),默认情况下返回3。

这是一个经常出现的问题,但没有单一的“正确”答案会让每个人满意。

类表示is-a关系,接口表示can-do行为。 我通常会遵循一些经验规则:

  • 除非您确定需要接口,否则坚持使用类(抽象/具体)。
  • 如果您确实使用接口,请将它们切片为非常具体的function。 如果一个接口包含多个方法,那么你做错了。

此外,大多数形状和人物(或吸血鬼)的例子通常都是现实世界模型的不良例子。 “正确”的答案取决于您的应用程序需要什么。 例如,你提到:

 class Vampire extends Monster implements Dangerous, Lethal, BloodSuckable 

您的应用程序确实需要所有这些接口吗? 有多少种不同类型的Monster ? 你真的有Vampire以外的课程来实施BloodSuckable吗?

尽量不要过多概括并在不需要时提取接口。 这可以追溯到经验法则:坚持使用简单的类,除非您的用例需要一个接口。

这是一个很好的问题。 这个问题有很多好的和坏的答案。 典型的问题是,抽象类和接口有什么区别? 让我们看看你在哪里使用抽象类以及在哪里使用接口。

在哪里使用抽象类:就OOP而言,如果存在inheritance层次结构,那么您应该使用抽象类来为您的设计建模。
在此处输入图像描述

在哪里使用接口:当您必须使用一个通用合同连接不同的合同(非相关类)时,您应该使用接口。 让我们以Collection框架为例。 在此处输入图像描述

Queue,List,Set与它们的实现有不同的结构。但它们仍然有一些共同的行为,如add(),remove()。 所以我们可以创建一个名为Collection的接口,并且我们已经在接口中声明了常见的行为。 如您所见,ArrayList实现了List和RandomAccess接口的所有行为。这样我们就可以在不改变现有逻辑的情况下轻松添加新合同。 这被称为“编码到接口”。

你的形状例子很好。 我是这样看的:

当您拥有共享的方法或成员变量时,您只有抽象类。 对于Shape的示例,您只有一个未实现的方法。 在这种情况下,始终使用接口。

假设你有一个Animal类。 每只动物都会追踪它有多少肢体。

 public abstract class Animal { private int limbs; public Animal(int limbs) { this.limbs = limbs; } public int getLimbCount() { return this.limbs; } public abstract String makeNoise(); } 

因为我们需要跟踪每只动物有多少肢,所以在超类中使用成员变量是有意义的。 但每只动物都会产生不同类型的噪音。

所以我们需要使它成为一个抽象类,因为我们有成员变量和实现的方法以及抽象方法。

对于你的第二个问题,你需要问问自己这个问题。

三角形总是会成形吗?

如果是这样,您需要从Shape界面扩展Triangle。

总而言之 – 使用您的第一组代码示例,选择接口。 使用最后一组,选择第二种方式。