Model-View-Controller
Keep what the program knows separate from how it is shown.
Three responsibilities:
- Model: the data and the rules. Knows nothing about the screen.
- View, displays the model. Holds no application logic.
- Controller, turns user input into operations on the model.
The point is the first line. A model that does not import any UI class can be tested without a screen, reused behind a different interface, and reasoned about on its own. Most of the benefit of MVC comes from that single constraint; the rest is detail.
How the model tells the view
The model must not call the view directly. That would reintroduce the dependency. Instead the view observes the model:
public class Document {
private final List<Listener> listeners = new ArrayList<>();
public void addListener(Listener l) { listeners.add(l); }
private void fireChanged() {
for (Listener l : listeners) l.documentChanged();
}
}
The model announces that something changed. Whoever is listening decides what that means. The model still knows nothing about the screen.
Swing’s version
Swing implements this, but splits it differently. Each component has a model, ListModel, TableModel, Document for text, while the view and controller are merged into the component itself, an arrangement usually called separable model or model-delegate.
The practical consequence is that you rarely write a controller class. You write a model, hand it to a component, and attach listeners:
JTable table = new JTable(myTableModel);
Get TableModel right and the table displays, sorts and updates itself.
The version most applications reach
Strict three-part MVC is often more ceremony than a small application needs. What survives, and is worth keeping in any size of program:
- Application state lives in classes with no UI imports.
- UI classes read that state and send it commands.
- Changes are announced, not polled.
If your window class contains business rules, or your model imports a GUI package, the separation has failed regardless of what the classes are named.
Threading
In Swing, all component access happens on the Event Dispatch Thread. Work triggered by the controller that takes real time must not run there or the interface freezes. Use a SwingWorker, and update components in done() or via SwingUtilities.invokeLater. This constraint is easy to forget and the symptom, an application that stops repainting, is unmistakable once you have seen it.