This may really be a Swing question, but that's only because I found out I wanted to address it by working with Swing's JSlider. I'm looking for a more generalized approach.
What I have is a very simplified case of the Observer pattern. My code only has two classes, a JFrame (which is my GUI and also the Observer class), and a Model (which is my document and also the Subject class). A more complete implementation of the pattern would include interface definitions, as well as registration and notification methods. I've left all that out and hard-coded the notification, in an effort to trim down my code to the smallest possible example.
The goal is to update the model (which merely contains an integer) by moving the slider, while also having the model notify the slider when the model's integer changes for any other reason, so the slider always matches the model. (As an example of a real-world case where you see this, consider the sliders that track playback on MP3 players; they move in real-time, but you can also grab them and set them where you want, meaning they react to user input, but also to changes in the current location of the playback stream).
Here's my GUI and main method:
The above code properly calls the model.set() method whenever the slider's state changes, which means the model is always set to match the position of the slider. Here's the Model definition:
Now, a classic problem arises in the above: when the user sets the slider, that changes its state, which results in the change listener created at Lines 14-20 being called, which calls the model's set() method, which notifies the observer, which sets its state to match the model, which (except for what I'm going to mention next) would cause the Observer to change its state, which would result in the change listener being called, and so on, and so on, until my stack overflowed.
That's not what happens, thanks to some code somewhat deep inside Swing. The JSlider makes use of a model all its own (truly, anyone wishing to learn model-view-controller patterns might do well to look at how JSlider and JComboBox are structured internally; they make great use of the MVC pattern, imho). It uses the DefaultBoundedRectangleModel, which compares each incoming new value (that is, a value it is being asked to set itself to) with its current value, and does not fire state-change events when the values are the same. Problem solved, eh? Indeed, even if the slider's model didn't check for redundant changes, the slider's own setValue() method queries the model and does the same check. Problem solved twice!
Oracle itself wrote:Ideally, each Swing component would be aware of its current value and the value that the view is trying to set it to. If they match, no change notifications will be sent. However, some Swing components include this logic, and others do not.
That same article suggests a few ways to cope with this, but it also mentions some drawbacks for each, with no clear (to me, anyway) guidance on which to use in any given case.
Now, the otherwise superb book, "Head-First Design Patterns," does a nice job of teaching the Observer pattern, and includes it in a good chapter covering MVC. But, they finesse the problem by isolating all the user controls that cause changes in the model's state from all the GUI objects that react to changes in the model's state. In their examples, the input/state-change/output sequence cannot result in, effectively, another input, because they've got different controls for input and output. In effect, their examples let you turn a knob, then see the value on a meter. My problem (as in an MP3 player) is that the knob and the meter are the same control.
I've messed around with a few alternative solutions, and done my due-diligence with Googling, yet don't feel I have a grip on a general policy (or policies) to guide me here. So, here are my questions:
Which Swing controls can be relied upon (and, when that depends on things like their underlying models, when can they be relied upon) to filter out redundant state changes?
Is it a good practical idea to solve this problem by overriding Swing control methods so they only update the model, and only the model updates the view? (I tried this with the slider, overriding setValue(). That only partially worked, as the slider still moves around in response to some other code, even though its final value was governed by my overridden method, and that did allow me to stop any redundant updates.)
Where else can one go for guidance on avoiding redundant updates when the input controls can have their values set in response both to user input and the model's notifications in the MVC pattern?
I always solve this problem in the 'Model' part of the code. The philosophy, after all, is that you want to inform listeners of changes, not necessarily that a value was set. So in the set method, compare the new value with the old value and only send the even if there is a change:
I would have assumed the Swing components did the same, and it looks like (as you said) in some cases it does. But I don't rely on my listeners being Swing components (or smart) so I would break the loop here.
Steve, that's the approach I like best, as I agree with your reasoning: The listeners count on the model to notify them, so the model should decide if there is anything they need to know. Here's the full excerpt, however, from the Oracle article I linked to above:
Once the application is up and running, you immediately run into a problem. Consider the following chain of events:
One of the Swing components in the view receives a change, presumably from the user action.
The appropriate controller method is called.
The model is updated. It notifies the controller of its property change.
The view receives a change event from the controller and attempts to reset the value of the appropriate Swing components.
The appropriate controller method is called, and the model is updated again.
At this point, any of three different scenarios can occur, depending on what Swing component you use and how robust your model is.
The Swing component that prompted the initial change refuses to update itself the second time, noting that its property state cannot be updated again while it is in the process of notifying listeners of the initial state change. This primarily occurs when you use Swing text components.
The model notes that the value of the second update matches that of the first, its current value, and refuses to send a change notification. This is always a safe programming practice, and it automatically occurs if you use the PropertyChangeSupport class provided in the java.beans package. However, it does not keep the model from receiving a redundant update.
No safeguards are in place on either the model or the Swing component, and the program enters an infinite loop.
This issue occurs because the Swing components are autonomous. For example, what happens if the user presses the up arrow of the JSpinner component in PropertiesViewPanel, incrementing the spinner's value by one? After the value is updated, a GUI event listener method that is listening for value changes is called, opacitySpinnerStateChanged(), which in turn calls the controller and then updates the appropriate property in the model.
With a traditional MVC design, the view would still contain the previous value, and the change in the model would update the view to the current value. However, there is no need to update the Swing component because it has already reset itself to the correct value -- it did so before it even passed an event to the controller.
How do you get around this? One way is to write a mechanism that tells the model or the controller not to propagate a change notification under these circumstances, but this is not a good idea. Remember that more than one view may be listening for changes on the model. If you shut down the change notification for the model, no other listeners, including other views, will be notified of the change. In addition, other components in the same view may rely on the property change notification, with a slider and spinner combination, for example.
I am very likely missing something, but it seems to me that the two passages I've highlighted in red, above, contradict each other in their subpassages I've rendered in big red letters. Except for redundant updates reaching the model, I really don't see why there's any reason to say it's "not a good idea." In particular, I really don't see how a properly implemented notification system would fail (as the article seems to suggest) to update all listeners, just because it chose not to update any listeners redundantly.
Can you make any better sense of the "not a good idea" comment, above?
I don't read that passage as saying not to do what I showed, it is saying not to generate a mechanism where the View tells the Model 'don't send change updates because I already know about the change' something like this:
This would be a bad idea, because it assumes that all listeners would be in a position where they don't need or want 'dirty updates' and could mean some listeners are in an inconsistent state. The situation where you validate the value as different than the current value is not the same. It is an internal model check, and ensures all listeners are up to date by always pushing a change, but never pushing a non-changing set.
The only problem with this is there is possibly one round of un-needed redundant calls. If the JSlider updates the Model, the model changes, and updates the slider. Assuming the slider was one of the bad components, it would take the change as real and update the model, and at this point the loop would stop. But there would be one or two unnecessary method calls.
Ah! Brilliant! I did read it wrongly. Thanks, pal.
With that settled, I'm satisfied that your approach is a good one. Yes, the model gets redundant updates, but "redundant" might be something only the model itself can ascertain. For example, if a model is reporting the highest reading in the last five minutes from several temperature sensors, it might decide to notify all views of a duplicate value, every five minutes, just so each view knows it is reporting data that isn't stale, even if the data value being reported hasn't changed in the last hour. (I suppose one could make the argument that the view should apply the highest-in-the-last-five-minutes logic, but that would seem to put duplicate work on any number of views that needed it, while also burdening those views with lots of updates they will mostly ignore.)