Interfaces and Abstract classes confusion in Java with examples
我很难理解什么时候使用接口而不是抽象类,反之亦然。另外,我很困惑什么时候用另一个接口扩展一个接口。对不起,这封长信,但这很让人困惑。
创建形状似乎是一个流行的起点。假设我们想要一种建模二维形状的方法。我们知道每个形状都有一个区域。以下两种实现之间的区别是什么:
带接口:
1 2 3 4 5 6 7 8 9 10 11 12 |
使用抽象类:
1 2 3 4 5 6 7 8 9 10 11 |
我理解抽象类允许您定义实例变量,并允许您提供方法实现,而接口不能做这些事情。但在本例中,这两个实现似乎是相同的。所以使用任何一个都可以吗?
但是现在假设我们想要描述不同类型的三角形。我们可以有等腰三角形、锐角三角形和直角三角形。对我来说,在这种情况下使用类继承是有意义的。使用"is-a"定义:直角三角形"is-a"三角形。三角形是一种形状。此外,抽象类应该定义所有子类中常见的行为和属性,因此这是完美的:
带抽象类
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 } } |
我们也可以通过接口来实现,三角形和形状是接口。但是,与类继承(使用"is-a"关系来定义应该是子类的内容)不同,我不确定如何使用接口。我看到两种方式:
第一种方式:
1 2 3 4 5 6 7 8 9 10 11 12 | 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; } } |
第二种方式:
1 2 3 4 5 6 7 8 9 10 | public interface Triangle extends Shape { public final int sides = 3; } public class RightTriangle implements Triangle { .... public double area(){ return .5 * height * base; } } |
在我看来,这两种方法都有效。但是你什么时候会用一种方式而不是另一种?与抽象类相比,使用接口来表示不同的三角形有什么好处吗?尽管我们把形状的描述复杂化了,但是使用接口和抽象类仍然是等价的。
接口的一个关键组件是,它可以定义可以在不相关的类之间共享的行为。所以一个可飞的接口将出现在类飞机和鸟中。因此,在这种情况下,很明显首选接口方法。
此外,要构建扩展另一个接口的混淆接口:在决定什么是接口时,什么时候应该忽略"is-a"关系?举个例子:链接。
为什么"verybadvampire"应该是类,"vampire"应该是接口?"吸血鬼"是一个"吸血鬼",所以我的理解是"吸血鬼"应该是一个超类(可能是抽象类)。"吸血鬼"类可以实现"致命"以保持其致命行为。此外,"吸血鬼"是一个"怪物",所以"怪物"也应该是一个阶级。"吸血鬼"类还可以实现一个名为"危险"的接口,以保持其危险行为。如果我们想创建一个新的怪物叫做"大老鼠",它是危险的,但不是致命的,那么我们可以创建一个"大老鼠"类,扩展"怪物"并实现"危险"。
上面的输出是否与使用"吸血鬼"作为接口(在链接中描述)的输出相同?我看到的唯一区别是,使用类继承和保留"is-a"关系可以消除很多混乱。但这并没有得到遵循。这样做的好处是什么?
即使你想让一个怪物分享吸血鬼的行为,你也可以重新定义物体的表现方式。如果我们想要一种新的吸血鬼怪物叫做"VerymildVampire",我们想要创造一种类似吸血鬼的怪物叫做"Chupacabra",我们可以这样做:
"吸血鬼"类扩展了"怪物"工具"危险"、"致命"、"吸血"
"VerymildVampire"类扩展了"Vampire"类
'chupacabra'类扩展'monster'实现'bloodsuckable'
但我们也可以这样做:
"VerymildVampire"扩展"Monster"工具危险、致命、吸血鬼
"丘帕卡布拉"扩展了"怪物"工具的危险,吸血鬼
第二种方法是创建一个"吸血鬼"接口,这样我们就可以更容易地定义一个相关的怪物,而不是创建一组定义吸血鬼行为的接口(如第一个例子)。但这打破了IS-A的关系。所以我很困惑…
使用抽象类或接口时请记住基本概念。
当要扩展的类与实现它的类更紧密地耦合时,即当两者都有父子关系时,使用抽象类。
例如:
1 2 3 4 5 | abstract class Dog {} class Breed1 extends Dog {} class Breed2 extends Dog {} |
然而,当实现类具有从类到实现的特性时,将使用接口。
1 2 3 4 5 6 7 8 | interface Animal { void eat(); void noise(); } class Tiger implements Animal {} class Dog implements Animal {} |
这是在设计类层次结构时会遇到的一个问题,这些层次结构有点像普通的那样复杂。但一般来说,在使用抽象类和接口时,您需要知道的事情很少
抽象类- 允许您利用使用构造函数和构造函数重写的能力
- 限制具有多个继承的类(如果要设计复杂的API,这尤其有用)
- 实例变量和方法实现
- 利用方法超级调用的能力(使用super调用父抽象类的实现)
界面
- 启用多继承-可以实现n个接口
- 只允许表示概念方法(没有方法体)
通常使用'-able'子句的接口(如功能)。Eg:
将抽象类用于类似is-a(演进格式)的内容。Eg:
但是硬性和快速性的规则不容易创建。希望这有帮助
如果要使一个或多个方法不是抽象的,请使用抽象类。
如果你想保持所有的抽象,使用一个接口。
这是个好问题。这个问题有很多好的和坏的答案。典型的问题是,抽象类和接口之间的区别是什么?让我们看看在哪里使用抽象类以及在哪里使用接口。
抽象类的使用位置:在OOP方面,如果存在继承层次结构,那么应该使用抽象类来建模设计。
在何处使用接口:当您必须使用一个公共契约连接不同的契约(非相关类)时,您应该使用一个接口。以集合框架为例。
队列、列表、集合与它们的实现具有不同的结构,但它们仍然共享一些常见的行为,如add()、remove()。所以我们可以创建一个名为collection的接口,并且我们已经在接口中声明了公共行为。如您所见,arraylist实现了来自list和randomaccess接口的所有行为,这样我们就可以轻松地添加新契约,而无需更改现有逻辑。这被称为"接口编码"。
这是一个经常出现的问题,但没有一个"正确"的答案能让每个人满意。
类表示is-a关系,接口表示can-do行为。我通常遵循一些经验法则:
- 坚持使用类(抽象/具体),除非您确定需要接口。
- 如果您确实使用接口,请将它们分割成非常具体的功能。如果一个接口包含的方法不止几个,那么您就错了。
此外,大多数人和形状的例子(或吸血鬼的事!)通常是现实世界模型的糟糕例子。"正确"的答案取决于您的应用程序需要什么。例如,您提到:
1 | class Vampire extends Monster implements Dangerous, Lethal, BloodSuckable |
您的应用程序真的需要所有这些接口吗?有多少种不同类型的
不要太过笼统,在您不需要接口的时候提取它们。这可以追溯到经验法则:坚持使用一个简单的类,除非您的用例需要一个接口。
这里有很多问题。但我认为基本上你是在问接口和抽象类。
通过接口,您可以拥有实现多个接口的类。但是,如果您想将接口用作API,那么它是不持久的。接口一旦发布,就很难修改,因为它会破坏别人的代码。
对于抽象类,只能扩展一个类。但是,抽象类对于API是持久的,因为在以后的版本中,您仍然可以修改,而不必破坏其他人的代码。同样,对于抽象类,您可以有预定义的实现。例如,在三角形示例中,对于抽象类,您可能有一个方法countedges(),它默认返回3。
你的体型很好。我是这样看的:
只有当有共享的方法或成员变量时,才有抽象类。对于您的
比如说你上了一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public abstract class Animal { private int limbs; public Animal(int limbs) { this.limbs = limbs; } public int getLimbCount() { return this.limbs; } public abstract String makeNoise(); } |
因为我们需要跟踪每个动物有多少肢体,所以在超类中使用成员变量是有意义的。但每只动物发出的噪音都不同。
所以我们需要把它变成一个抽象类,因为我们有成员变量、实现方法以及抽象方法。
对于第二个问题,你需要问问自己。
三角形总是一个形状吗?
如果是这样,则需要从形状界面延伸三角形。
因此,最后——使用第一组代码示例,选择接口。对于最后一组,选择第二种方式。