43

假设我们有两个类, Tiger并且Aeroplane. 这两种类型的共同点是速度。我知道创建一个超类然后从它派生子类是不合逻辑的ClassWithSpeed。相反,最好创建一个包含该方法的接口,然后在and中实现它。我明白了。但是,我们可以在没有接口的情况下做同样的事情。我们可以定义方法in和方法in 。唯一(可能非常大)的缺陷是我们无法通过接口引用“访问”对象。AeroplaneTigerspeed()AeroplaneTigerspeed()Aeroplanespeed()TigerTigerAeroplane

我是 Java 和 OOP 的初学者,如果有人向我解释接口的作用,我将不胜感激。干杯!

4

12 回答 12

74

它的:

public int howFast(Airplane airplane) {
    return airplane.speed();
}

public int howFast(Tiger tiger) {
    return tiger.speed();
}

public int howFast(Rocket rocket) {
    return rocket.speed();
}

public int howFast(ThingThatHasntBeenInventedYet thingx) {
    // wait... what? HOW IS THIS POSSIBLE?!
    return thingx.speed();
}

...

对比

public int howFast(Mover mover) {
    return mover.speed();
}

现在,想象一下将来必须返回并更改所有这些howFast功能。

接口与否,每个类都必须实现或继承该speed()方法。接口使您可以更轻松地使用这些不同的类,因为它们都承诺以某种方式运行。您将调用speed(),它们将返回一个int,因此您不必为每个可能的类编写单独的方法。

当您发现由于相对论物理学的突破而需要以不同方式处理速度时,您只需更新调用speed(). 当您的曾孙女为 编写一个类时HyperIntelligentMonkeyFish,她不必反汇编您的古老二进制文件并进行更改,以便您的代码可以监视和控制她的突变军队。她只需要声明HyperIntelligentMonkeyFish implements Mover

于 2013-05-26T16:15:53.707 回答
8

接口允许 Java,因为它是静态类型的,在语言设计者认为值得的程度上解决多重继承限制。

在动态语言(如 Python、Ruby 等)中,您可以在任意对象上调用 speed 方法,并在运行时发现它是否存在。

另一方面,Java 是静态类型的,这意味着编译器必须同意该方法将提前存在。在不共享共同祖先并且可能只有共同对象的类上执行此操作的唯一方法是接口。

由于对象可以实现任意数量的接口,这意味着您可以获得多重继承的好处(单个对象可以为不同的方法构成多个不同的对象)而没有缺点(两个超类中的冲突实现)。

在 Java 8 中,该语言的设计者得出结论,没有任何实现的接口过于严格(我猜他们不喜欢我的解决方案;-)),因此他们允许默认实现,从而扩展了接口的多继承选项,同时仍然试图通过一组复杂的优先规则来避免冲突的实现问题,以便有一个明确的默认实现来执行。

于 2013-05-26T17:42:03.763 回答
6

我试图用下面的例子来解释接口的优势——

假设您有另一个类需要使用老虎或飞机作为参数。因此,通过使用接口,您可以使用

someObject.someMethod(ClassWithSpeed)

但如果你不使用界面,你可以使用

someObject.someMethod(Tiger)
someObject.someMethod(AeroPlane)

现在你应该怎么做?你可能的答案是,"I will use two overloaded method".

到目前为止还可以,但是

假设您需要添加更多选项(例如汽车、自行车、兔子、乌龟……还有 100 个其他选项)。那么您将如何更改现有代码?你需要改变很多东西..

上面的整个示例只有一个目的。也就是说

“我们需要界面来创建更好、可重用和正确的面向对象设计”

注意

  1. 如果您确定程序太小并且您永远不需要更改它们,那么我认为可以在没有接口的情况下实现
于 2013-05-26T16:17:26.643 回答
5

定义接口允许您定义不仅适用于 Airplane 和 Tiger 的方法,而且适用于共享相同接口的其他类。

例如,假设您的接口是 IObjectWithSpeed。然后,您可以定义这样的方法——在对 IObjectWithSpeed 对象进行操作的单个类中。

  public double calculateSecondsToTravel( IObjectWithSpeed obj, double distance ) {
       return distance / obj.getSpeed();
  }

接口允许我们满足开闭原则——对扩展开放,对修改关闭。不需要修改上述方法的单个实现,因为定义了实现 IObjectWithSpeed 的新类。

于 2013-05-26T16:14:10.090 回答
3

我想深入了解很多理论细节,但会尝试使用这个例子来解释。

考虑JDBC API。它是用于处理 Java 中与数据库相关的选项的 API。现在,行业中有这么多的数据库。如何为此编写驱动程序?好吧,快速而肮脏的方法可能是使用我们自己的类和 API 编写自己的实现。

但是从程序员的角度考虑。他们会在使用不同的数据库时开始学习DATABASE DRIVER的 API 吗?答案是不。

那么问题的解决方案是什么?只要有一个定义明确的 API,任何人都可以为自己的实现进行扩展。

在 JDBC API 中,有一些接口,它们是 Connection、ResultSet、PreparedStatement、Statement 等。现在每个数据库供应商都会实现该接口并为此编写自己的实现。结果 ?:减少了开发人员的工作量并且易于理解。

现在,这个自定义实现可能包括什么?这很简单。他们做什么,以 ResultSet 接口为例并实现它,并以任何返回 ResultSet 的方法,返回实现ResultSet 接口的类,如下所示

结果集 rs=new ResultSetImpl(); //这就是他们在内部做的事情。

所以接口就像合约。它们定义了您的类能够做什么,并为您的应用程序提供了灵活性。您可以正确使用接口创建自己的 API。

希望这对您有所帮助。

于 2013-05-26T16:33:38.347 回答
3

接口不仅仅是一个方法签名。

它是一种代表合同的类型。合同是事情,而不是方法签名。当一个类实现一个接口时,是因为有一个共享行为的契约,该类有兴趣将其实现为一个类型。该契约是通过指定的成员实现的,这些成员通常是方法体,但也可能包括静态最终字段。

老虎和飞机可能都可以表示为具有通过 speed() 实现的共同行为的类型......如果是这样,那么接口代表表达该行为的合同。

于 2013-05-26T16:39:17.593 回答
3

除了描述这些类的功能之外,接口的方法可以在不了解实现它的类的情况下使用,即使对于尚未定义的类也是如此。

因此,例如,如果您需要一个Transport计算有效路线的类,可以给它一个ClassWithSpeed作为参数实现的类,并使用它的方法speed()来计算它需要什么。这样,您可以将它与我们的类一起使用Aeroplane,也可以与我们稍后定义的任何类一起使用,比如Boat. Java 会注意,如果您想使用一个类作为它的参数,Transport它将实现ClassWithSpeed,并且任何实现该方法的类都ClassWithSpeed实现该方法speed()以便可以使用它。

于 2013-05-26T16:21:05.053 回答
1

这是一个非常广泛的问题,可以给出一个简单的答案。我可以推荐一本书Interface Oriented Design: With Patterns。它解释了接口的所有功能。以及为什么我们不应该避免它们。

于 2013-05-26T17:51:30.953 回答
1

在语言级别上,接口的唯一用途是,就像您提到的那样,能够以一种通用的方式引用不同的类。然而,在人员层面上,情况却有所不同:IMO Java 在设计和实现由不同的人(通常来自不同的公司)完成的大型项目中使用时非常强大。现在,系统架构师无需在 Word 文档上编写规范,而是可以创建一堆类,然后实现者可以直接将这些类插入到他们的 IDE 中并开始处理它们。换句话说,它更方便,而不是声明“X 类实现方法 Y 和 Z 以便将其用于目的 A,只是说“X 类实现接口 A”

于 2013-05-27T07:00:25.773 回答
1

编译器使用接口(作为一种语言结构)来证明方法调用是有效的,并允许您让依赖类与实现类交互,同时尽可能少地了解实现类。

于 2013-05-26T16:17:15.813 回答
1

您是否尝试过使用组合来代替?如果我想要 2 个不同的类来继承相同的能力,我会使用一个类,该类通过使用抽象类并检查实例来获取其工作类型的对象。接口对于强制将方法包含在类中很有用,但不需要任何实现,也不需要 2 个编码团队在不同的编码领域工作。

Class Tiger {
  public MovingEntity mover;

 public Tiger(){

  mover.speed=30;
  mover.directionX=-1;
  mover.move(mover);

 }

}
Class Plane {
  public MovingEntity mover;

 public Plane(){
  mover.speed=500;
  mover.directionX=-1;
  mover.move(mover);

 }


Abstract Class Moverable(){
  private int xPos;
  private int yPos;
  private int directionX;
  private int directionY;
  private int speed;


Class MovingEntity extends Moverable {
 public void move(Moverable m){
   if(m instanceof Tiger){

      xPos+=directionX*speed;
      yPos+=directionY*speed;

   }else if(m instanceof Plane){
      xPos+=directionX*speed;
      yPos+=directionY*speed;

   }
 }
于 2013-05-27T08:30:13.280 回答
0

因为创建接口为您提供了多态性,跨越所有这些类,即 Tiger 和 Aeroplane。

当基类的功能也将成为子类功能的核心时,您可以从(抽象或具体)类扩展。

当您想在类中添加除核心功能之外的增强功能时,您可以使用接口。因此,即使它不是您的类的核心功能(因为您已经通过实现接口签订了合同),使用接口也会为您提供多态性。与为每个类创建 speed() 方法相比,这是一个巨大的优势。

于 2013-05-26T23:42:01.317 回答