【问题标题】:Howto design for extension如何设计扩展
【发布时间】:2009-03-19 15:18:34
【问题描述】:

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

【问题讨论】:

  • 你不应该在不带参数的方法中抛出 IllegalArgumentException...
  • @markt 为了“争论”,我们假设它是 IllegalStateException :)

标签: java inheritance class-design


【解决方案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 方法,您也可以将其设为 public final。

【讨论】:

  • 我明白了这一点,但我仍然缺少一种同时实现 validate() 方法“好”和“实用”的方法。如果 Tree 实现最终的 validateEx() 并调用 validate(),则扩展 Tree 的类(比如说 Forest)将无法覆盖 validateEx().Solution?Implement validateExEx()?
  • 那么,请使用更好的名称。也许是 ValidateTreeEx()。
  • 当然 - 他们不能替换你的代码.. 但是 validate() 怎么会被调用呢?
  • 我已经使用了建议的解决方案,但现在我遇到了更大的问题:抽象类中的空非抽象方法(PMD 规则违规)。有什么办法可以解决吗?
  • @lux 实现的类不调用 validate()。每当需要验证对象时,应用程序中的现有代码都会调用该函数。这样,实现类就不能替换或隐藏我回答中代码的现有验证逻辑:只能扩展它。
【解决方案2】:

虽然 Joel Coehoorn 的回答解释了如何克服 OP 发布的具体问题,但我想提出一种方法,可以更广泛地了解“如何设计扩展?” 正如 OP 在他的一个 cmets 中指出的那样,给定的解决方案不能随着(类)继承深度的增加而很好地扩展。此外,由于明显的原因,在基类中预期需要验证可能的子类 (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 章)

【讨论】:

  • 感谢 Horst,尽管您的回答讨论了示例代码的设计,而不是原始问题:如何设计扩展。这实际上只是示例代码,可能并不完美。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-10-25
  • 1970-01-01
  • 2023-03-19
  • 1970-01-01
  • 2016-09-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多