重写getPreferredSize()会破坏LSP

我总是在这个站点中看到覆盖getPreferredSize()建议,而不是使用setPreferredSize() ,例如这些先前的线程中所示。

  1. 使用重写getPreferredSize()而不是对固定大小的组件使用setPreferredSize()
  2. 我应该避免在Java Swing中使用set(Preferred | Maximum | Minimum)Size方法吗?
  3. 重写setPreferredSize()和getPreferredSize()

看这个例子:

 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已设置为非null值,则返回它 。 如果UI委托的getPreferredSize方法返回非null值,则返回该值; 否则遵从组件的布局管理器。

所以这样做显然打破了Liskov替代原则 。

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 states Sets the preferred size of this component. 但不是如何。

这个有趣问题的几个方面(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 preferred:true 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(有充分的理由)。 在它的后端到awt它变成了一个从未有过的bean属性。

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

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

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

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

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

因为getXxxSize假设为布局管理器提供了大小提示,实际上似乎没有任何合理的理由将setXxxSize方法public因为,恕我直言,开发人员不应该搞乱它们 – 需要一个组件根据自己的内部要求提供所需尺寸的最佳估计。

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

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

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

如果你要记录忽略setXxxSize的决定会构成Liskov替换原则的中断吗? 可能,因为组件仍然可以像它的父母那样行事。

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

根据你的例子,你不应该重写getXxxSizesetXxxSize ,而是从构造函数中调用setXxxSize ,因为这将保持当前的API契约,但也会setXxxSize从构造函数调用可覆盖方法的脚趾。 。

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

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

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