如何设计扩展

时间:2009-03-19 15:18:35

标签: java inheritance class-design

有一个Checkstyle规则DesignForExtension。它说:如果你有一个公共/受保护的方法,它不是抽象的,也不是最终的也不是空的,它不是“为扩展而设计的”。请阅读description for this rule on the Checkstyle page了解相关理由。

想象一下这个案子。我有一个抽象类,它定义了一些字段和这些字段的验证方法:

public abstract class Plant {
    private String roots;
    private String trunk;

    // setters go here

    protected void validate() {
        if (roots == null) throw new IllegalArgumentException("No roots!");
        if (trunk == null) throw new IllegalArgumentException("No trunk!");
    }

    public abstract void grow();
}

我还有Plant的子类:

public class Tree extends Plant {
    private List<String> leaves;

    // setters go here

    @Overrides
    protected void validate() {
        super.validate();
        if (leaves == null) throw new IllegalArgumentException("No leaves!");
    }

    public void grow() {
        validate();
        // grow process
    }
}

遵循Checkstyle规则,Plant.validate()方法不是为扩展而设计的。但是在这种情况下我如何设计扩展?

2 个答案:

答案 0 :(得分:17)

该规则正在抱怨,因为派生(扩展)类可以在不告诉您的情况下完全替换您提供的功能。这强烈表明您尚未充分考虑如何扩展类型。它想要你做的是这样的事情:

public abstract class Plant {
    private String roots;
    private String trunk;

    // setters go here

    private void validate() {
        if (roots == null) throw new IllegalArgumentException("No roots!");
        if (trunk == null) throw new IllegalArgumentException("No trunk!");
        validateEx();
    }

    protected void validateEx() { }

    public abstract void grow();
}

请注意,现在有人仍然可以提供自己的验证码,但它们无法替换您预先编写的代码。根据您使用validate方法的意图,您也可以将其设为公开最终版。

答案 1 :(得分:1)

虽然Joel Coehoorn的答案解释了如何克服OP发布的具体问题,但我想建议一种更广泛地看待“如何设计扩展?”的方法。 正如OP在他的一篇评论中指出的那样,给定的解决方案不能很好地扩展(类)继承深度。此外,由于显而易见的原因,在基类中预期验证可能的子类(validateTreeEx())的需要是有问题的。

建议:在施工时检查植物属性并完全删除validate()(以及可能的设置者;另请参阅http://www.javaworld.com/article/2073723/core-java/why-getter-and-setter-methods-are-evil.html)。原始代码表明validate()是一个不变量,在每次grow()操作之前必须为真。我怀疑这个设计是故意的。如果没有可以在施工后“破坏”工厂的操作,则无需一遍又一遍地重新检查有效性。

更进一步,我质疑初始继承设计的健全性。没有额外的(可能是多态的)操作,Tree只是重用了Plant的一些属性。我坚持认为,类继承不应该用于代码重用。 Josh Bloch有这样的说法(来自Effective Java,第2版,第4章):

  

如果你在合成适当的地方使用继承,那么你   不必要地暴露实施细节。最终的API与您联系在一起   到原来的实现,永远限制了性能   你的班。更严重的是,通过揭露内部,你让   客户端直接访问它们。

同时查看'第17项:继承的设计和文件,否则禁止它'(同一本书的第4章)