类继承抽象类并实现接口

10

上个周末我读了一些关于接口、抽象类和设计原则的内容。最后我有点困惑,于是尝试根据所学(或者我以为我学到的)建立一个例子。

这是我的例子:模拟一个保存关于树的信息的类。

首先,我会创建一个接口:

public interface Tree{
     public void grow();
}

接口包含应由具体树实现的所有方法。到目前为止一切顺利,但这样的树需要一些在所有树系列中共享的属性(变量)。为此,我将使用抽象类:

public abstract class AbstractTree implements Tree {
    private String barColor;
    private int maxHeight;
    private boolean isEvergreen;
}

这是正确的方式吗?还是我不能够创建一种关于应该在其他类中的属性(变量)的合同?

当属性部分完成之后,我想要有3种类型的树。

  • 橡树
  • 枫树
  • 云杉

因此,每个树“类型”都可以具有单独的变量。

public class OakTreeImpl extends AbstractTree{
    private String barColor;
    private int maxHeight;
    private boolean isEvergreen;
    private String foo;
    @Override
    public void grow() {
    }   
}

这种方法在面向对象设计原则方面是否正确,还是我完全错了?


3
AbstractTree 应该使用 protected 字段,这样你就不需要在实现中重新声明它们。 - OneCricketeer
为什么不只使用抽象类呢? - VedantK
我在抽象类中将它们声明为受保护的并从Impl类中移除它们?即使我在类中没有直接“看到”它们,我仍然可以访问(获取/设置)变量的值吗? - user2742409
@user2742409 正确 - Draken
@VedantKekan 我想强制Impl-Class声明接口中的状态函数。抽象类中的函数也是必须的吗?我使用接口,因为如果另一个类(如Player)在函数中使用Tree,我会做一些像chop(Tree tree)这样的事情。使用抽象类是否相同? - user2742409
2
那么为什么不在抽象类中声明成员访问呢?例如:public int getMaxHeight(){return this.maxHeight;}您仍然可以在实现中稍后重写它们,但这并不是强制性的。如果您想强制重写,则在抽象类中声明它们为 public abstract int getMaxHeight(); - Draken
3个回答

8

这种方法确实可行,但在这种情况下,界面已经完全过时,所以并没有太多意义。
你应该像这样将grow方法添加到AbstractTree中:

public abstract class AbstractTree{
    protected String barColor;
    protected int maxHeight;
    protected boolean isEvergreen;

    public abstract void grow();
}

使用接口是有意义的,如果您想要不同类型的植物,这些植物都应该能够生长。
interface Plant{
    void grow();
}

abstract class Tree implements Plant{
    void grow(){ /* do sth */ }
}

abstract class Flower implements Plant{
    void grow(){ /* do sth totally different */
}

接口的目的是在多个类中提供相同的方法,但这些方法有不同的实现方式;而抽象类则提供了在所有子类中共享的方法和属性。 如果抽象类中的某个方法也是抽象的,那么每个子类都必须自己实现它。

非常感谢,现在我明白了,希望如此 :D - user2742409

7

我更愿意将实例变量标记为protected

因为超类的所有受保护成员都可以被子类访问。仅当父类和子类在同一个包中时才能访问

public abstract class AbstractTree implements Tree {
    protected String barColor;
    protected int maxHeight;
    protected boolean isEvergreen;
}

public class OakTreeImpl extends AbstractTree{

    // I can access barColor, maxHeight, isEvergreen in this class

    @Override
    public void grow() {

    }   
}

你是完全正确的,但我想000000000000000000000的答案更适合我的问题,因为我没有很清楚地表达我的问题。但还是谢谢你的努力。 - user2742409

6
尽管这可能部分是主观的,但我必须赞同迄今为止给出的其他答案。当您想要建模Tree时,应该有一个Tree接口,明确说明每个Tree所具有的方法。特别地,我建议不要简单地用AbstractTree类替换它。一些人说你几乎根本不应该使用抽象类(例如Jaroslav Tulach在“实用API设计”中)。至少我会说,您应该非常保守地使用它们。最重要的是:您应该尝试避免让抽象类出现在其他类的公共接口中。例如,如果您有另一个类/接口,其中包含像下面这样的方法:
void makeGrow(Tree tree) {
    System.out.println("Growing "+tree);
    tree.grow();
}

将此处出现的 Tree 替换为 AbstractTree 将会降低灵活性。您将永远无法使用不继承自 AbstractTree 的类,考虑到您只能从一个类继承,这可能是一个严重的限制。(您总是可以实现多个接口 - 因此接口在这里不会限制灵活性)。
但即使您使用基于抽象的类,我建议保守使用 protected 字段。或者更一般地说,要意识到继承类的影响,如 Joshua Bloch 在 "Effective Java" 中所述的“第17条 - 为继承设计和记录,否则禁止继承”。
在许多情况下,您不希望继承类完全访问字段。因此,您应该至少考虑将字段设置为 private,并仅为您想要授予继承类的访问提供 protected 方法。
public interface Tree{
    public void grow();
}

abstract class AbstractTree implements Tree {

    // Do the values of these fields ever change? If not,
    // then make them final, and set them only in the
    // constructor
    private final String barColor;
    private final int maxHeight;
    private final boolean evergreen;

    protected AbstractTree(...) { ... }

    // Subclasses are only allowed to read (but not write) these fields
    protected final String getBarColor() { return barColor; }
    protected final intgetMaxHeight() { return maxHeight; }
    protected final boolean isEvergreen() { return evergreen; }
}

1
你宣称的设计哲学并不违背我的观点。然而,我认为如果你有一个用于树的抽象类,你不应该创建一个被称为“Tree”的单独接口,而这个接口只会被抽象树类使用。这没有任何意义!接口只有在你不知道谁会实现它或需要这种额外的灵活性时才有用。我们在这种情况下使用抽象的唯一原因是因为你不应该能够实例化一个新的树,而不指定它是什么类型(枫树、橡树等)。 - 000000000000000000000
1
@000000000000000000000 "...但仅由抽象树类使用" - 我认为这就是重点:你想要传递给接收树的方法什么?你想要传递AbstractTree(尽管名称如此,但它是具体的,因为它必须扩展AbstractTree),还是想要能够传递任何Tree,无论它是如何实现的?(即使这个interface实现不基于AbstractTree)?再次强调,这可能在某种程度上是主观的,我的方法可能看起来像“过度设计”,但我更喜欢在这里使用接口。 - Marco13
1
顺便说一下:我真的不明白为什么你认为可能有理由为“植物”接口辩护,但没有为“树”接口辩护。这只是一个概念层次结构的“根”应该是什么的问题,我不明白这如何反对将“Tree”建模为接口。 - Marco13
我今天发布的第二个问题是想在类的另一个方法中传递任何类型的树,以便我可以保留您提供的参数@Marco13。同时也感谢您的回答和努力 :) - user2742409

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接