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

2022-09-02 22:09:48

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

创建形状似乎是一个流行的起点。假设我们想要一种对 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;
    }

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

但现在说我们想要描述不同类型的三角形。我们可以有等腰,锐角和直角三角形。对我来说,在这种情况下使用类继承是有意义的。使用“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
    }
}

我们也可以用接口来做到这一点,三角形和形状就是接口。但是,与类继承(使用“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”关系?举个例子:链接

为什么“VeryBadVampire”应该是一个类,而“Vampire”应该是一个接口?一个“VeryBadVampire”是一个“吸血鬼”,所以我的理解是“吸血鬼”应该是一个超类(也许是抽象类)。“吸血鬼”类可以实现“致命”以保持其致命行为。此外,“吸血鬼”是“怪物”,所以“怪物”也应该是一个类。“Vampire”类还可以实现一个名为“Dangerous”的接口,以保持其危险行为。如果我们希望创建一个名为“BigRat”的新怪物,它是危险的,但不是致命的,那么我们可以创建一个“BigRat”类,它扩展了“Monster”并实现了“Dangerous”。

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

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

“吸血鬼”类扩展 “怪物” 实现 “危险”、“致命”、“吸血鬼”
“非常吸血鬼”类扩展“吸血鬼”类
“丘帕卡布拉”类扩展“怪物”实现“吸血鬼”

但我们也可以这样做:

“VeryMildVampire”扩展 “Monster”实现危险,致命,吸血鬼
“Chupacabra”扩展“Monster”实现危险,吸血鬼

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


答案 1

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

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

例如:

       abstract class Dog {}

       class Breed1 extends Dog {}

       class Breed2 extends Dog {}

Breed1并且是狗的两种类型,并且作为狗有一些共同的行为。Breed2

然而,当实现类具有可以从类实现中获取的功能时,将使用接口。

     interface Animal {
         void eat();
         void noise();
     }

     class Tiger implements Animal {}

     class Dog  implements Animal {}

Tiger并且是两个不同的类别,但既吃又发出声音,这是不同的。所以他们可以使用吃和噪音从.DogAnimal


答案 2

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

如果要保留所有抽象,请使用接口。