Leepoint Java Reference Notes on the Java language and its standard library.

How the UI and the model talk

Downward by method call, upward by event. Never the other way round.

Two directions, and they are not symmetric.

View to model is a plain method call. The view holds a reference to the model and calls it:

model.setName(nameField.getText());

Model to view must not be a method call, because the model must not know the view exists. It is an event:

public void addChangeListener(ChangeListener l) { listeners.add(l); }

The view registers itself; the model announces that something changed; whoever is listening reacts.

Why the asymmetry matters

If the model calls the view directly, the model depends on the UI toolkit. That single dependency costs you the ability to test the model without a display, to show the same data in two places at once, and to reuse the logic behind a different interface.

Keep it one-way and all three are free. A second view registers as another listener; neither knows about the other.

What to put in the event

Two styles.

Thin — “something changed”, and the view re-reads what it needs. Simple, hard to get wrong, and can be wasteful if the model is large and the view redraws everything.

Fat — the event carries what changed, so the view updates only that. Faster, and more to maintain.

Start thin. Move to fat where a profiler says to, which for most applications is nowhere.

Do not poll

A timer that reads the model every 100ms to see whether it changed works, and it is the wrong shape: it burns cycles doing nothing, and it introduces a delay for no reason. If you find yourself polling an in-memory object, you wanted a listener.

Threading

In Swing, components may only be touched on the Event Dispatch Thread. A model that fires a change event from a background thread will call view code on that thread, and the symptoms — a component that paints wrongly, or occasionally not at all — are intermittent and awful to chase.

Either fire events on the EDT, or have the view marshal:

SwingUtilities.invokeLater(() -> label.setText(model.getStatus()));

Deciding once, per project, which side is responsible for that is worth more than any amount of debugging later.