1

我有一个超类 Animal,它有两个子类 Cat 和 Dog。狗和猫都需要有特定的说话和移动方法。以下是实现此目的的两种选择:

  1. Animal 会有两个抽象方法 move() 和 speak();Dog、Cat 和 Dog 分别覆盖这两个方法,因此它们是特定于它们的。
  2. 我可以有一个具有通用动物方法 move() 和 speak() 的接口,两个子类实现这些方法,因此它们再次特定于它们。

这些方法中的一种是否比另一种更合适?我意识到如果我有一个动物的 ArrayList,我会写:

ArrayList Animal<allAnimals> = new ArrayList <Animal>():
allAnimals.add(new Dog());
allAnimals.add(new Cat());

//If I override the methods, I can simply go: 
for (Animal a: allAnimals) a.move();

//Where as if I implemented the methods, I would not need be able to do this. I would need to cast the animal as its specific animal. e.g. If I knew the animal was a dog.

Dog aDog= (Dog) a; 
a.move();

因此,根据使用的情况,覆盖和实施可能具有某些优点和缺点。其他人可以详细说明吗?

4

7 回答 7

3

作为一般经验法则,当且仅当您的扩展类将共享大量行为时,才使用扩展(抽象类)。在这种情况下,可以使用扩展来消除重复代码的需要,这是一件好事(TM)。

另一方面,如果您的类只需要具有相同的签名,则使用更灵活的接口(例如,您可以实现多个接口,但您只能从一个类扩展)。

这只是一些通用的建议,因为每个用例都是不同的。为了避免扩展的限制,可以使用其他技术,例如组合:(http://en.wikipedia.org/wiki/Composition_over_inheritance)。

最后但并非最不重要的一点是,我认为您的代码略有错误。您可以以完全相同的方式调用基类上的方法,无论它们是从抽象类还是接口继承。

于 2013-04-14T21:44:39.730 回答
3

首先,从子类的角度来看,重写或实现方法没有区别。因此,您编写的代码在所有情况下都将完全相同(在 Java 中,所有方法都像 C++virtual函数一样工作;调用的方法是被引用的实际类之一)。

因此,决定是定义一个超类,它是一个接口、一个抽象类或一个可实例化的类。

new Animal()如果创建超类( )的对象有意义,则应使用可实例化类。

抽象类,当没有超类的对象但某些方法的实现将由所有(或几乎所有)子类共享。

接口,当您只定义合同并且实现留给每个子类时。

这取决于问题的范围来决定使用哪种方式。例如,如果move()只是表示“更改动物的 x,y 坐标”,则可能它应该是抽象类中的实现方法。但是,如果您必须定义动物如何移动,那么Animal接口会更好,因此每个子类都定义它。

于 2013-04-14T21:45:30.770 回答
1

一般来说,经验法则是使用一个接口,如果你可以使一个 Java 类可以实现许多接口但只能扩展一个类。

只有当几个类有很多共同点并且您希望能够重用相同的代码时,才应该使用扩展(继承)。

顺便说一句,在您作为示例提供的代码中,您可以使用:

for (Animal a: allAnimals) a.move();

在您使用接口和继承的情况下。

于 2013-04-14T21:44:19.990 回答
1

在这种情况下,它可能取决于您是否希望拥有 move() 的默认实现,因此 Dog 和 Cat 不必提供自己的实现。大概 move() 方法在猫和狗之间有很多共同点,因此您可以通过超类中的通用实现具有更好的可重用性。

于 2013-04-14T21:45:33.023 回答
1
//Where as if I implemented the methods, I would not need be able to do this. I would need to cast the animal as its specific animal. e.g. If I knew the animal was a dog.

Dog aDog= (Dog) a; 
a.move();

不,你根本不必这样做。如果Animal是接口而不是抽象类,那么您为抽象类显示的过程就可以正常工作。

于 2013-04-14T21:47:11.403 回答
1

从概念上讲,抽象类描述事物的本质,接口描述事物的作用。在这种情况下,将 Animal 作为抽象类是合适的,因为 Cat 或 Dog 是 Animal。在功能上,选择导致最少编码量/最干净的类层次结构的任何选项。

于 2013-04-14T21:50:13.007 回答
0

首先,您有第三种选择,即:拥有一个接口(例如 MoverAndSpeaker;虽然这名字有点蹩脚),并拥有抽象基类Animal 'implmenent' 它:

static public abstract class Animal implements MoverAndSpeaker {
    @Override
    public abstract void move();
    public abstract void speak();
}

为什么你甚至想要这样的东西?

与这里的一些答案不同,我认为语义一致性是最重要的考虑因素,避免代码重复等问题要么自行解决,要么只占少数。

如果我处于你的位置,我将应用的关键标准是:

“有没有不是动物的物体会移动和说话?”

  • 如果答案是“不”,那么移动和说话是动物会做的事情。因此,仅使用抽象方法
  • 如果答案是“是”,那么我问自己

“所有的动物都会移动和说话吗?”

  • 如果第二个答案是“是”,那么使用上面的代码(假设您不希望所有动物都有一些默认实现)。
  • 如果第二个答案是“否”,则只考虑接口。尽管您可能想要在 Dog、Cat 和 Animal 之间建立一个中间类 - 如果您在所有也会移动和说话的 Animals 之间有一些共享代码或字段。

最后,如果所有动物都没有共同点,那么正如其他答案中所建议的那样,您可能根本不需要 Animal 基类。

于 2013-04-15T09:35:35.877 回答