【问题标题】:Wicket and the 'constructor calls overridable method' PMD warningWicket 和“构造函数调用可覆盖的方法”PMD 警告
【发布时间】:2011-06-09 10:53:41
【问题描述】:

我们通过将大部分构造函数代码移至onInitialize() 来避免此 PMD 警告。但是我们只是将问题(设计缺陷?)转移到不同的地方吗?

即我们的onInitialize() 只是一个代理构造函数,PMD 没有注意到吗?

当您在构造函数中调用可覆盖的方法时,我们遇到了那种弹出的问题,但这似乎源于 Wicket 本身调用了一个(找不到确切的源代码行,但onInitialize(),一个可覆盖的方法,当你在构造函数中调用 add() 时最终会被调用)。

如果有帮助,很乐意提供示例代码。

public class PageA extends WebPage {

    protected SomeBean bean;

    public PageA() {
        add(new Label("foo", "bar"));
        bean = new SomeBean();
    }

}

public class PageB extends PageA {

    public PageB() {
        super();
    }

    @Override
    protected void onInitialize() {
        add(new Label("rofl", bean.getSomeText()));
    }
}

您会认为这很好,但对onInitialize 的调用不会发生在您认为会发生的地方:

在页面调用add()时,方法流程为:

MarkupContainer    add()    
MarkupContainer    addedComponent() 
Page               componentAdded()
MarkupContainer    initialize()
Component          fireInitialize()
Component          onInitialize()

因此,您可以看到,如果您将组件添加到 WebPageonInitialize() 方法将被触发,这是一个可覆盖的方法,导致上述看起来正常的代码实例创建 NullPointerExceptions。

你得到的唯一警告是onInitialize()的JavaDoc:

注意:此调用的时间不准确,约定是在 {@link Component#onBeforeRender()} 之前的某个时间调用它。

【问题讨论】:

标签: java oop wicket pmd


【解决方案1】:

如果您只从容器的onInitialized() 方法中将组件添加到容器,则不会出现此问题。但它不能被 PMD 验证,至少不能被内置规则验证。

不过,我不认为这是一个设计缺陷。这是一个设计决定。您不能将所有设计都基于静态分析工具和预定义规则。 API 可用性也是设计的一个重要方面,有时甚至比设计原则更相关。

例如,CQS (Command-Query Separation) 原则要求某事(改变状态)的方法不应该返回任何东西,而返回某事的方法不应该返回任何东西有任何副作用(改变状态)。

如果这是一个硬性规则,则无法实现流畅的接口(改变对象状态并返回this,允许方法链接的方法)。 Wicket 广泛使用它(几乎所有组件操作方法都返回this),这是让它使用起来很有趣的原因之一。

PMD 是一个非常有用的工具。但你必须是工具的主人,而不是它的奴隶。您应该将其警告视为可能存在的问题,但如果您对自己的设计选择有信心,只需将代码标记为要绕过并感到高兴。

【讨论】:

    【解决方案2】:

    考虑以下类:

    public class A {
    
        public A() {
            System.out.println(val().toString());
        }
    
        protected Integer val() {
            return 0;
        }
    
    }
    

    乍一看还不错,假设val() 永远不会返回null,事实就是如此。现在B 子类A

    public class B extends A {
    
        private final Integer i;
    
        public B() {
            //super();    //implicit
            i = 1;
        }
    
        @Override
        protected Integer val() {
            return i;
        }
    }
    

    B 乍一看也很好——它从val() 返回i,而null 永远不会是null,因为它是在构造函数中初始化的final。但是创建B 实例会抛出NullPointerException。你能看出为什么吗? 提示:查看隐式super() 发生的位置。

    您认为移动i 初始化会有所帮助吗?为什么不呢?

    private final Integer i = 1;
    

    根据经验,永远不要从非最终类的构造函数中调用非私有方法。事实上,在这种情况下,我什至会触发编译错误。如您所见,此问题与 Wicket 无关,将初始化移动到 onInitialize() 是为了避免此类陷阱。

    【讨论】:

    • 嗨,感谢您在 cmets 中的回答和有用的链接,但我已经明白为什么在构造函数中调用可覆盖的方法是不好的。我正在尝试确定链接的 onInitialize 调用是否会导致相同的问题。
    • 当然,将其移出一个单独的方法违反了另一个原则,即在构造之后,您的对象必须始终处于一致状态,并且其方法必须可以按任何顺序调用而不会失败。跨度>
    【解决方案3】:

    当您在构造函数中调用可重写方法时,我们遇到了那种弹出的问题,但这似乎源于 Wicket 本身调用一个(找不到确切的源代码行,但 onInitialize(),一个可覆盖的方法,最终在构造函数中调用 add() 时被调用)。

    我很确定当您调用 add(component) 时调用的 onInitialize() 方法是正在添加的组件的 onInitialize() 方法(add 方法的参数),而不是您当前正在构建的类的方法.这应该没问题,因为该组件已经完全构建好了。

    【讨论】:

    • 请查看示例代码,了解如何在调用 add 后触发 onInitialized
    猜你喜欢
    • 2012-04-08
    • 2013-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-21
    • 2011-08-31
    • 2011-03-25
    • 2014-01-18
    相关资源
    最近更新 更多