java 如何设计扩展

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/662598/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 13:17:50  来源:igfitidea点击:

Howto design for extension

javainheritanceclass-design

提问by Eduard Wirch

There is a Checkstylerule DesignForExtension. It says: if you have a public/protected method which is not abstract nor final nor empty it is not "designed for extension". Read the description for this rule on the Checkstyle pagefor the rationale.

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

Imagine this case. I have an abstract class which defines some fields and a validate method for those fields:

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

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

I have also a subclass of Plant:

我还有一个 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
    }
}

Following the Checkstyle rule the Plant.validate() method is not designed for extension. But how do I design for extension in this case?

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

采纳答案by Joel Coehoorn

The rule is complaining because it is possible for a deriving (extending) class to completely replace the functionality you provided without telling you about it. It's a strong indication that you haven't fully considered how the type might be extended. What it wants you to do instead is something like this:

这条规则是有问题的,因为派生(扩展)类可能会在不告诉您的情况下完全替换您提供的功能。这是一个强有力的迹象,表明您还没有完全考虑如何扩展该类型。它希望你做的是这样的:

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

Note that now someone can still supply their own validation code, but they can't replace your pre-written code. Depending on how you meant to use the validatemethod you could also make it public final instead.

请注意,现在有人仍然可以提供他们自己的验证代码,但他们无法替换您预先编写的代码。根据您打算如何使用该validate方法,您也可以将其设为public final。

回答by Horst Dehmer

Although the answer by Joel Coehoorn explains how to overcome the concrete problem posted by the OP, I'd like to suggest an approach which takes a broader view on ‘how to design for extension?' As the OP points out in one of his comments, the given solution does not scale well with a growing (class) inheritance depth. Also, anticipating in the base class the need to validate possible child classes (validateTreeEx()) is problematic for obvious reasons.

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

Proposal: Check a plants properties at construction time and remove validate()altogether (along with possible setters; see also http://www.javaworld.com/article/2073723/core-java/why-getter-and-setter-methods-are-evil.html). The original code suggests that validate()is an invariant, which has to be true before each grow()operation. I doubt that this design is intentional. If there is no operation, which can ‘break' a plant after construction, there is no need to re-check the validity over and over again.

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

Going even further, I'd question the soundness of the initial inheritance design. Without additional (possibly polymorphic) operations, Treejust reuses some properties of Plant. I hold the strong opinion, that class inheritance should not be used for code reuse. Josh Bloch has this to say (from Effective Java, 2nd Edition, chapter 4):

更进一步,我会质疑初始继承设计的合理性。没有额外的(可能是多态的)操作,Tree只需重用 Plant 的一些属性。我坚信,类继承不应该用于代码重用。Josh Bloch 有这句话(来自 Effective Java,第 2 版,第 4 章):

If you use inheritance where composition is appropriate, you needlessly expose implementation details. The resulting API ties you to the original implementation, forever limiting the performance of your class. More seriously, by exposing the internals you let the client access them directly.

如果在适合组合的地方使用继承,则不必要地暴露实现细节。生成的 API 将您与原始实现联系起来,永远限制了您的类的性能。更严重的是,通过公开内部结构,您可以让客户端直接访问它们。

Also check out 'Item 17: Design and document for inheritance or else prohibit it' (also chapter 4 for the same book)

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