如何设计扩展

2022-09-02 10:35:47

有一个 Checkstyle 规则 DesignForExtension。它说:如果你有一个公共/受保护的方法,它不是抽象的,也不是最终的,也不是空的,它不是“为扩展而设计的”。阅读“检查样式”页面上此规则的说明,了解基本原理。

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

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();
}

我还有一个植物的子类:

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() 方法不是为扩展而设计的。但是在这种情况下,我如何设计扩展?


答案 1

该规则之所以抱怨,是因为派生(扩展)类可能会在不通知您的情况下完全替换您提供的功能。这强烈表明您尚未完全考虑如何扩展该类型。相反,它希望你做的是这样的:

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


答案 2

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

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

更进一步,我会质疑初始继承设计的合理性。无需其他(可能是多态)操作,只需重用Plant的某些属性。我强烈认为,类继承不应用于代码重用。Josh Bloch有这样的说法(来自 Effective Java,第 2 版,第 4 章):Tree

如果在组合合适的位置使用继承,则无需公开实现详细信息。生成的 API 将您与原始实现联系在一起,从而永远限制了类的性能。更严重的是,通过公开内部,您可以让客户端直接访问它们。

另请查看“项目17:设计和文档继承,否则禁止继承”(同一本书也包括第4章)


推荐