18

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

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

我们也可以使用接口来做到这一点,Triangle 和 Shape 就是接口。但是,与类继承不同(使用“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 将出现在飞机类和鸟类中。因此,在这种情况下,显然首选接口方法。

此外,为了构建扩展另一个接口的令人困惑的接口:在决定什么应该是接口时,什么时候应该忽略“IS-A”关系?举个例子:LINK

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

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

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

“Vampire”类扩展“Monster”实现“Dangerous”、“Lethal”、“BloodSuckable”
“VeryMildVampire”类扩展“Vampire”类
“Chupacabra”类扩展“Monster”实现“BloodSuckable”

但我们也可以这样做:

'VeryMildVampire' 扩展了 'Monster' 工具危险、致命、吸血鬼
'Chupacabra' 扩展了 'Monster' 工具危险、吸血鬼

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

4

7 回答 7

6

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

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

例如:

       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 {}

TigerDog是两个不同的类别,但都吃和发出声音,这是不同的。所以他们可以使用eat and noise from Animal

于 2012-11-16T07:36:53.553 回答
3

Use an abstract class when you want to make one or more methods not abstract.

If you want to keep all abstract, use an interface.

于 2012-07-09T05:14:10.927 回答
3

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

抽象类

  • 允许您利用使用构造函数和构造函数覆盖的强大功能
  • 限制类具有多重继承(如果您正在设计复杂的 API,这特别有用)
  • 实例变量和方法实现
  • 利用方法超级调用的力量(使用超级调用父抽象类的实现)

界面

  • 启用多重继承 - 您可以实现 n 个接口
  • 只允许表示概念方法(无方法体)

通常将接口用于“-able”子句(如功能)。例如:-

  1. Runnable
  2. Observable

对 is-a(进化格式)之类的东西使用抽象类。例如:-

  1. Number
  2. Graphics

但硬性规定并不容易制定。希望这可以帮助

于 2012-07-09T06:05:32.103 回答
1

这是一个经常出现的问题,但没有一个“正确”的答案可以让所有人都满意。

类代表is-a关系,接口代表可以做的行为。我通常遵循一些经验规则:

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

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

class Vampire extends Monster implements Dangerous, Lethal, BloodSuckable

您的应用程序真的需要所有这些接口吗?Monsters 有多少种不同的类型?您实际上有除Vampire该工具之外的其他类BloodSuckable吗?

尽量不要泛化太多,并在不需要时提取接口。这又回到了经验法则:除非您的用例需要接口,否则请坚持使用简单的类。

于 2012-07-09T05:35:09.673 回答
1

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

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

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

于 2012-07-09T05:25:34.797 回答
1

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

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

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

Queue、List、Set 与它们的实现有不同的结构。但它们仍然有一些共同的行为,如 add()、remove()。因此我们可以创建一个名为 Collection 的接口,并且我们在接口中声明了通用行为。如您所见,ArrayList 实现了 List 和 RandomAccess 接口的所有行为。这样做我们可以轻松添加新合约,而无需更改现有逻辑。这被称为“接口编码”。

于 2014-06-11T15:29:35.633 回答
0

Your shape example is good. I look at it this way:

You only have abstract classes when you have methods or member variables that are shared. For your example for Shape you've only got a single, unimplemented method. In that case always use an interface.

Say you had an Animal class. Each Animal keeps track of how many limbs it has.

public abstract class Animal
{
    private int limbs;
    public Animal(int limbs)
    {
        this.limbs = limbs;
    }

    public int getLimbCount()
    {
        return this.limbs;
    }

    public abstract String makeNoise();
}

Because we need to keep track of how many limbs each animal has, it makes sense to have the member variable in the superclass. But each animal makes a different type of noise.

So we need to make it an abstract class as we have member variables and implemented methods as well as abstract methods.

For your second question, you need to ask yourself this.

Is a Triangle always going to be a shape?

If so, you need to have Triangle extend from the Shape interface.

So in conclusion - with your first set of code examples, choose the interface. With the last set, choose the second way.

于 2012-07-09T05:14:46.000 回答