在Java中处理循环事件的优雅方式?

我觉得这对我来说不是一个特定的问题; 以前每个人都可能遇到过这个问题。 为了正确说明它,这是一个简单的UI:

alt text http://sofzh.miximages.com/java/www.freeimagehosting.net

如您所见,这两个微调器控制着一个变量 – “A”。 唯一的区别是他们使用不同的视图来控制它。

由于这两个微调器的显示值是同步的,因此出现循环事件。

如果我更改顶部微调器,将更改“A”,并且底部微调器的值也将相应更新。 但是 ,更新底部微调器的调用(例如setValue)也会触发另一个事件,指示顶级微调器根据底部微调器的值进行更新。 因此创建一个错误的循环,最终可能导致StackOverFlowexception。

我以前的解决方案有点麻烦:我放置了一个保护布尔值来指示是否应该执行第二次更新调用。

现在我想问一下“ 我怎样才能优雅地处理这种情况?一般来说,不是特定于纺纱厂 )”

谢谢


更新:

由于我有2个答案建议我使用观察者结构,我不得不说些什么。

就像我说的那样,它很棒但远非完美。 不仅因为其固有的复杂性,而且还因为它无法解决问题

为什么? 要了解原因,您必须在Java Swing中实现View和Model-Controller的紧密耦合 。 让我们以我的微调器UI为例。 假设变量A实际上是Observer对象。 然后,在从顶部微调器触发第一个状态更改事件后,观察者“A”将更新其值并触发PropertyChange事件以通知底部微调器。 然后是第二次更新,它更新了底部微调器的视图。 但是 ,更改底部微调器的视图不可避免地会触发冗余事件,该事件将尝试再次设置“A”的值。 然后,完全构造致命循环并抛出堆栈溢出。

理论上,Observer模型试图通过引入2个独立的反馈路径来解决直接循环。 链式更新赔率(在事件响应代码中)隐含地形成连接两个路径的桥,再次进行循环。

回到模型 – 视图 – 控制器,想一想您的模型是什么,以及您的视图是什么。

在当前的实现中,您有两个模型(每个Spinner控件一个),并且它们将通过View层同步。

你应该做的是共享相同的支持模型。 对于具有减去值的微调器,请为原始模型创建代理。 即:

class ProxySpinnerModel implements SpinnerModel { getValue() { return originalSpinner.getValue() - 10 } setValue(v) { originalSpinner.setValue(v+10) } } spinnerA = new JSpinner() spinnerB = new JSpinner( new ProxySpinnerModel( spinnerA.getModel() ) ) 

现在,您不需要添加侦听器,因为它们都使用相同的模型,并且默认实现(originalModel)已经具有更改侦听器,它将触发到视图。

问题解决了


我有很多不同的建议。 特别是,我要感谢Marc W&Reverend Gonzo。 我在这里总结一下这些想法; 这可以节省您通过大量文本导航的时间。

如果仔细分离视图和模型控制器,则可以轻松绕过此问题。 死循环是由依赖写入引起的: write_1 -> write_2 -> write_1 ->... 直观地说,打破依赖可以优雅地解决问题。

如果我们深入研究问题,我们可以发现更新相应的视图并不一定涉及外部写调用。 实际上,视图仅取决于它所代表的数据。 众所周知,我们可以重新编写逻辑如下: write_1 -> read_2 & write_2 -> read_1

为了说明这个想法,让我们比较不同海报提到的3种方法: alt text http://sofzh.miximages.com/java/www.freeimagehosting.net

正如您所看到的,只有代理视图才能解决所有依赖关系,因此它是解决此问题的通用解决方案。

在实践中,它可以像这样实现(在您的事件响应代码中):

  setValue(newValue); anotherSyncUI.parse(); // not anotherSyncUI.setValue() any more anotherSyncUI.repaint(); 

没有更多的循环。 解决了。

它有点复杂,但你可以使A实际上是一个可观察的对象。 两个微调器(或任何需要根据A的值更新自身)都会观察到A 每当A改变时,微调器(或者再次,任何对象)都会更新自己以反映A的新值。 这使得微调器的逻辑彼此分离。 在你的例子中,旋转器不应该相互耦合,因为它们实际上彼此无关。 相反,它们应该简单地绑定到A并且单独处理它们自己的视图更新。

每当更改第一个微调器中的值时,您只需更新A的值以匹配它。 每当第二个微调器中的值发生更改时,您当然会在将其赋值给A之前将其值加10。

更新

在回答原始问题的更新时,我的回答是,微调器不会听取彼此的更改事件。 为每个微调器都有一个单独的事件处理方法。 用户单击微调器中的向上或向下箭头会产生与以编程方式调用微调器上的setValue不同的事件,对吗? 如果旋转器完全彼此独立,则不会有无限循环。

例如,对于第二个微调器,计算A-10然后将其与微调器的当前值进行比较。 如果它是相同的,什么也不做,结束无限循环。 同样对于第一个微调器。

我认为还有一些方法可以以不会触发事件的方式更新微调器的模型,但我不知道它们在我的头脑中。

对两个JSpinner使用单个SpinnerModel。 请参阅以下代码:请注意,只有在其中一个JSpinner定义新值时,才会调用setValue()。

 import java.awt.BorderLayout; import javax.swing.*; public class Test { public static void main(String[] args) { JFrame jf = new JFrame(); SpinnerModel spinModel = new MySpinnerModel(); JSpinner jspin1 = new JSpinner(spinModel); JSpinner jspin2 = new JSpinner(spinModel); jf.setLayout(new BorderLayout()); jf.add(jspin1, BorderLayout.NORTH); jf.add(jspin2, BorderLayout.SOUTH); jf.pack(); jf.setVisible(true); jf.setDefaultCloseOperation(3); } } class MySpinnerModel extends AbstractSpinnerModel { private int _value = 0; private int _min = 0; private int _max = 10; @Override public Object getNextValue() { if (_value == _max) { return null; } return _value + 1; } @Override public Object getPreviousValue() { if (_value == _min) { return null; } return _value - 1; } @Override public Object getValue() { return _value; } @Override public void setValue(Object value) { System.out.println("setValue(" + value + ")"); if (value instanceof Integer) { _value = (Integer) value; fireStateChanged(); } } } 

看来你真的在观察错误的事情。 从给出的示例中,我假设您要检测的是用户对控件的操作,而不是值本身的更改。 正如您所概述的那样,模型中的更改会反映在微调器的值中,而这就是形成无限循环事件的过程。

但是,进一步深入UI实现可能不是您想要的答案。 在那种情况下,我会说你能做的最好的事情就是当前的防护解决方案,或者更好地将逻辑提取到你的模型中(类似于Marc和William所说的)。 如何做到这一点将取决于提供的谜题的特定实施背后的“现实世界”模型。

通常,您的模型不应由GUI定义。 即,支持每个JSpinner的SpinnerModel不应该是你的值A.(这将是一个非常不优雅的紧密耦合依赖于特定视图。)

相反,您的值A应该是POJO或对象的属性。 在这种情况下,您可以向其添加PropertyChangeSupport。 (并且可能在任何情况下都已经这样做了,因为如果你的程序的其他部分改变了A,你希望你的微调器自动更新)。

我意识到这与Marc W的答案类似,你担心它“复杂”,但PropertyChangeSupport几乎为你做了所有这些。

实际上,对于简单的简单情况,您可以使用将“setProperty”方法连接到“firePropertyChange”调用的单个类(以及将值存储在HashMap中以用于任何“getProperty”调用)。

我真的不想解决你的问题,但我发现它很有趣。 我已经面对它并且每次以不同的方式解决它。 但当我想到’为什么?’ 而不是’怎么样?’ 我很困惑。
这个问题只存在,因为我使用的是自动机(MVC),它必须帮助我,并且正是以这种方式。 使用组件的艺术如何使这种自​​动化成为美丽代码的障碍。
为什么set #setEvent()必须生成与GUI操作相同的事件?

虽然,我的观点也非常接近观察者模式,但它比那个轻一点!

将A作为带有setter的变量

 private Integer A; setA(int A) { this.A = A; refreshSpinners(); } refreshSpinners() { setSpinnerA(); setSpinnerAMinus10(); } setSpinnerA() { // show value of A } setSpinnerAMinus10() { // show value of A-10 }