重写getPreferredSize()会破坏LSP
我总是在这个站点中看到覆盖getPreferredSize()
建议,而不是使用setPreferredSize()
,例如这些先前的线程中所示。
- 使用重写getPreferredSize()而不是对固定大小的组件使用setPreferredSize()
- 我应该避免在Java Swing中使用set(Preferred | Maximum | Minimum)Size方法吗?
- 重写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替换原则吗? 是(基于可用的文档)。
但是大部分对象扩展都没有? 如果必须严格遵守原始实现的期望,那么改变方法行为的重点是什么(是的,当你应该这样做时有很好的例子,比如hashcode
和equals
以及其他线条变灰的情况) ?
在这种情况下,问题似乎从setXxxSize
的不当使用以及这些方法实际上是public
这一事实延伸出来。 他们为什么公开? 我不知道,因为它们是导致更多问题的原因,而不仅仅是API的任何其他部分(包括KeyListener
)。
覆盖getPreferredSize
是首选,因为对象携带更改,而不是从对象所有权/上下文外部调用setPreferredSize
因为getXxxSize
假设为布局管理器提供了大小提示,实际上似乎没有任何合理的理由将setXxxSize
方法public
因为,恕我直言,开发人员不应该搞乱它们 – 需要一个组件根据自己的内部要求提供所需尺寸的最佳估计。
以这种方式覆盖getXxxSize
的原因还在于防止其他人更改您指定的值,这可能是由于特定原因造成的。
一方面,正如您所建议的那样,我们对API有所期待,但另一方面,当您不希望用户更改值时,我们有时会想要控制大小和很多次。
我个人的感觉是尽可能地忽略setXxxSize
(或将其视为protected
)。 覆盖getXxxSize
的原因之一是阻止人们更改大小,但同样,您可以覆盖setXxxSize
并抛出不支持的exception。
如果你要记录忽略setXxxSize
的决定会构成Liskov替换原则的中断吗? 可能,因为组件仍然可以像它的父母那样行事。
我的一般直觉是要了解Liskov Substitution Principle正在尝试做什么,知道何时应该使用它以及什么时候不应该使用它。 不能满足每种情况的明确规则,特别是当您考虑设计本身错误的情况时。
根据你的例子,你不应该重写getXxxSize
或setXxxSize
,而是从构造函数中调用setXxxSize
,因为这将保持当前的API契约,但也会setXxxSize
从构造函数调用可覆盖方法的脚趾。 。
所以无论你看到什么,你都会踩到别人的脚趾……
缺一切。 如果它对您很重要(维护Liskov替换原则),则应在您自己的组件上下文中使用setXxxSize
。 这样做的问题在于,不可能阻止某人用自己的价值观来消除你的设计决策,正如我在评论中所说,当人们在没有真正了解他们正在做的事情的情况下做到这一点时,这只会让每个人的工作成为一场噩梦。
不要滥用setPreferredSize
,只能在对象实例的上下文中使用它并拒绝从外部调用它…恕我直言