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.

Separate them first

The single most useful structuring decision in a program with an interface is to keep the user interface apart from the model, the business logic, the problem domain, the part that would still exist if the program had no screen.

The test: could you replace the interface? Without rewriting the model, could you drive it from a command line, or from a test, instead of from windows? If yes, the separation is real. If the answer needs a rewrite, it is not.

The model must know nothing about the interface. This is the asymmetry the rest of this page is about, and it is not negotiable. The interface knows about the model; the model does not know the interface exists. A model that imports a widget class, or prints a message for a user to read, has already lost the property that made it testable.

Splitting the interface side further into a view that displays and a controller that handles input, MVC, is a refinement of this, not a substitute for it. Get the model out first.

Errors travel as exceptions or return values, never as messages. It is tempting to have the model report a problem by writing text somewhere. Don’t: that decides how the problem is presented, which is the interface’s job. Throw, or return a value that says what happened, and let the interface decide whether that becomes a dialog, a red field or a log line.

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.