重写getPreferredSize()会破坏LSP

时间:2014-01-10 19:33:33

标签: java swing jcomponent liskov-substitution-principle

我总是在此网站中看到覆盖getPreferredSize()的建议,而不是使用setPreferredSize(),例如这些先前的主题中所示。

  1. Use of overriding getPreferredSize() instead of using setPreferredSize() for fixed size Components
  2. Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?
  3. Overriding setPreferredSize() and getPreferredSize()
  4. 见这个例子:

    public class MyPanel extends JPanel{
    
      private final Dimension dim = new Dimension(500,500); 
    
      @Override
      public Dimension getPreferredSize(){
          return new Dimension(dim);
      }
    
     public static void main(String args[]){
          JComponent component = new MyPanel();
          component.setPreferredSize(new Dimension(400,400));
          System.out.println(component.getPreferredSize());
     }
    
    }
    

    setPreferredSize()

      
        
    • 设置此组件的首选大小。
    •   

    getPreferredSize()

      
        
    • 如果preferredSize已设置为非空值,则返回。如果UI委托的getPreferredSize方法返回非null   价值然后返回;否则,请遵循组件的布局   管理器。
    •   

    这样做可以明显打破Liskov Substitution Principle

    prefferedSize是绑定属性,因此在设置时会执行firePropertyChange。所以我的问题是,当您覆盖getPrefferedSize()时,是否还需要覆盖setPreferredSize(..)

    示例:

     public class MyPanel extends JPanel{
    
      private Dimension dim = null; 
    
      @Override
      public Dimension getPreferredSize(){
          if(dim == null)
           return super.getPreferredSize();
          return new Dimension(dim);
      }
    
      @Override
      public void setPrefferedSize(Dimension dimension){
            if(dim == null)
                dim = new Dimension(500,500);
            super.setPreferredSize(this.dim); //
      }
    
     public static void main(String args[]){
          JComponent component = new MyPanel();
          component.setPreferredSize(new Dimension(400,400));
          System.out.println(component.getPreferredSize());
     }
    
    }
    

    现在我们看到我们得到了相同的结果,但是听众会得到真实值的通知,而且我们不会破坏LSP导致setPreferredSize状态Sets the preferred size of this component.而不是如何。

2 个答案:

答案 0 :(得分:5)

一般来说,这个问题没有简单(或正确)的答案。

重写getPreferredSize是否打破Liskov替换原则?是(基于可用的文档)。

但是大多数对象扩展都没有?如果必须严格遵守原始实现的期望,那么改变方法行为的重点是什么(是的,当你应该这样做时有很好的例子,比如hashcodeequals和其他线条变灰的人?)

在这种情况下,问题似乎从setXxxSize的不当使用以及这些方法实际上是public的事实延伸出来。他们为什么公开?我不知道,因为它们引起的问题多于API的任何其他部分(包括KeyListener)。

首选覆盖getPreferredSize,因为对象携带更改,而不是从对象所有权/上下文外部调用setPreferredSize

因为假设getXxxSize为布局管理器提供了大小调整提示,实际上似乎没有任何合理的理由让setXxxSize方法public实际上具有,{IMO}开发人员不应该搞乱他们 - 需要一个组件根据自己的内部要求提供所需大小的最佳估计。

以这种方式覆盖getXxxSize的原因还在于阻止其他人更改您指定的值,这可能是由于特定原因造成的。

一方面,正如您所建议的那样,我们对API有所期待,但另一方面,有时我们想要控制大小和很多次,当您不希望用户改变价值。

我个人的感觉是尽可能地忽略setXxxSize(或将其视为protected)。覆盖getXxxSize的原因之一是阻止人们更改大小,但同样,您可以覆盖setXxxSize并抛出不受支持的异常。

如果你要记录忽视setXxxSize的决定,那将构成Li​​skov替代原则的中断吗?可能,因为组件仍然可以像它的父母那样行事。

我的一般直觉是要了解Liskov Substitution Principle正在尝试做什么,知道何时应该使用它以及何时不应该使用它。不能满足每种情况的明确规则,特别是在考虑设计本身错误的情况时。

根据您的示例,您不应该覆盖getXxxSizesetXxxSize,而是从构造函数中调用setXxxSize,因为这会保留当前的API合约,但是也踩过从构造函数中调用可覆盖方法的脚趾......

所以无论你看到什么,你都会踩到别人的脚趾......

缺乏一切。如果它对您很重要(维护Liskov替换原则),则应在您自己的组件上下文中使用setXxxSize。这样做的问题在于,不可能阻止某人用自己的价值观来消除你的设计决策,正如我在评论中所说的那样,当人们在没有真正了解他们正在做的事情的情况下这样做时,这只会让每个人的工作成为噩梦

不要滥用setPreferredSize,只能在对象实例的上下文中使用它并拒绝从外部调用它...恕我直言

答案 1 :(得分:5)

这个有趣问题的几个方面(Mad已经提到了备用我的伙伴开发人员)

我们是否在覆盖getXXSize()时违反了LSP(也与setXXSize()相比)?

如果我们正确地执行它,那就不行了:-)第一个权限是属性的API文档,最好来自其来源,即组件:

  

将此组件的首选大小设置为常量值。对getPreferredSize的后续调用将始终返回此值。

这是一个绑定合同,所以无论我们实现getter,它必须遵守常量值,如果设置:

@Override
public Dimension getPreferredSize() {
    // comply to contract if set
    if(isPreferredSizeSet())
        return super.getPreferredSize();
    // do whatever we want
    return new Dimension(dim);
}

XXSize是绑定属性 - 是吗?

在JComponent的祖先中只有间接证据:实际上,Component会在setter中触发PropertyChangeEvent。 JComponent本身似乎记录了这个事实(由我加粗):

  

@beaninfo     首选:是的      bound:true     description:组件的首选大小。

哪个是......明显错误:作为绑定属性意味着只要值发生变化就需要通知侦听器,即以下(伪测试)必须通过:

JLabel label = new JLabel("small");
Dimension d = label.getPreferredSize();
PropertyChangeListener l = new PropertyChangeListener() ...
    boolean called;
    propertyChanged(...) 
        called = true;
label.addPropertyChangeListener("preferredSize", l);
label.setText("just some longer text");
if (!d.equals(label.getPreferredSize())
   assertTrue("listener must have been notified", l.called); 

......但失败了。由于某种原因(不知道为什么这可能认为合适),他们希望xxSize的常量部分成为绑定属性 - 这样的叠加是根本不可能的。本来可以(疯狂地猜测)一个历史性的问题:最初,只有Swing才能使用setter(有充分的理由)。在它的backport中,它变成了一个从未有过的bean属性。