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