﻿# Separating the model from the view

> A working flashcard trainer with everything in one Swing class is split, step by step, into a model with no Swing in it and a view that only forwards events and redraws itself with one refresh() – then the model gets JUnit tests.

Source: https://laszlonemes.com/university/programming-technology/model-view · Author: László Nemes

Session 6 · Programming Technology

A working flashcard trainer with everything in one Swing class is split, step by step, into a model with no Swing in it and a view that only forwards events and redraws itself with one refresh() – then the model gets JUnit tests.

14 min read `State vs rules vs presentation` `Model without Swing` `One refresh() method` `IllegalStateException guards` `JUnit tests for the model`

In the Swing session the calculator already kept its arithmetic in a separate class. This session is about that separation itself. We take a small program that works but has everything crammed into one class – a flashcard trainer – and turn it into a **model–view** (MV) program, one step at a time. From the outside the two versions are identical: same window, same buttons, same behaviour. Only the code is organised differently, and that difference is what makes the second version testable and easy to extend.

You will do exactly this with the game of the second assignment, so read the two versions side by side: `Flashcards` (the starting point) and `FlashcardsMV` (the result).

After this session you can

- Tell state, rules and presentation apart in an existing Swing class.
- Move the state and the rules into a model class with no Swing in it.
- Write a view whose event handlers only forward to the model and then call one refresh().
- Guard a model with IllegalStateException and test it with JUnit, without a window.
- Map the flashcard structure onto your own board game.

> **The rule of thumb:** the model knows *what is true* (the cards, the score, the rules); the view knows *how it looks*. The view asks, the model decides – and the model never knows a window exists.

## The starting point: everything in the GUI

The program reads question–answer pairs from a text file, one card per line, with a `~` between question and answer. It shows the question, *Toggle Q/A* reveals the answer, and *Good answer* / *Wrong answer* moves on to the next card – a good answer is worth one point. At the end it shows the result; *Reset* starts again with the same cards.

cards.txt

```text
What is an abstract method?~A method without a body; the subclasses must implement it.
Which Swing class represents a top-level window?~JFrame
```

In the starting version, `FlashcardsGUI` does everything. Four of its fields are not components at all – they are the **state** of the program:

```java
// FlashcardsGUI (before) – four of its fields are not components but STATE
private List<Card> cards = new ArrayList<>();   // the loaded cards
private int answered;                           // cards answered so far
private boolean showingQuestion = true;         // question or answer on screen
private int score;                              // good answers

private final JTextArea display;                // ...and these are the view
private final JLabel statusLabel;
private final JButton toggleButton;
```

The **rules** live in the event handlers: `answer` decides whether a point is due, whether there is another card, and that the next card starts with its question. It works, and at this size it is even readable. The trouble starts when you want to test or extend it:

- **It can't be tested.** To check that the practice really ends after three cards, you have to click through the window. A JUnit test can't call the rules, because they are tangled with `display.setText` and `statusLabel.setText`.
- **Screen updates are scattered.** There are three update methods, and every handler has to know which ones to call. Toggle calls one, answer calls three. Add a feature – shuffling, say – and it is easy to forget one, so the window disagrees with the state.
- **Unrelated things are mixed.** Reading the file and the `~` separator have nothing to do with Swing, yet they sit in the GUI class. A console or web version would mean rewriting everything.

> **Fig. 1** · The same five responsibilities before and after. Three of them move into a class that has never heard of Swing; the two that stay in the view get simpler.
>
> *Diagram:* The starting Flashcards program keeps state, rules, file reading, components and three update methods in one class. In FlashcardsMV the state, the rules and the file reading move to FlashcardsModel, which has no Swing in it; the components and a single refresh method stay in FlashcardsGUI.

## Separating them, step by step

This is the recipe that turned `Flashcards` into `FlashcardsMV`. Use the same one when your assignment game ends up in one class – or, better, plan the two columns before you write the first line.

1. **Find the state.** Which fields are *not* Swing components? Here: `cards`, `answered`, `showingQuestion`, `score`. They become the model's fields – all `private`; the view sees them only through query methods.
2. **Find the rules.** Which code changes those fields, and under what conditions? Those become the model's operations (`load`, `reset`, `toggle`, `answer`). Of the event handler, only "call the model, then refresh" remains.
3. **What isn't about the screen goes to the model too.** Reading the file, the `~` separator, the message about a malformed line – you would need all of these even if the GUI were replaced tomorrow.
4. **One `refresh()`.** Instead of scattered update methods, one method redraws the whole window from the model, and every handler calls it at the end. The view becomes "dumb", which is good: there is nothing in it to get wrong.
5. **Write tests for the model.** Now you can. The tests also document the rules: a test called `theNextCardAlwaysStartsWithItsQuestion` says more than a comment.

Step 2 in practice – before and after

```java
// Flashcards (before): the rule and the screen update in one method
private void answer(boolean correct) {
    if (answered < cards.size()) {
        if (correct) {
            score++;
        }
        answered++;
        showingQuestion = true;
        updateDisplay();      // the handler has to know which parts
        updateStatus();       // of the window this change affects
        updateButtons();
    }
}
```

```java
// FlashcardsModel (after): only the rule
public void answer(boolean correct) {
    requireCurrentCard();     // no current card: IllegalStateException
    if (correct) {
        score++;
    }
    answered++;               // moving on: the current card is cards.get(answered)
    showingQuestion = true;   // a new card always starts with its question
}

// FlashcardsGUI (after): the handler only forwards
model.answer(correct);
refresh();
```

> **Fig. 2** · The dependency points one way. The view and the tests use the model through the same public methods; nothing points back.
>
> *Diagram:* The view calls the model's operations and reads its queries. The JUnit tests call exactly the same methods without opening a window. The model never calls the view and imports nothing from javax.swing or java.awt.

|  | Flashcards | FlashcardsMV |
| --- | --- | --- |
| Where the state is | fields of the GUI | `FlashcardsModel` (private fields, queries) |
| Where the rules are | in the event handlers | in the model's operations |
| Updating the window | 3 methods, each handler picks | one `refresh()` from the model |
| Reading the file | in the GUI | in the model, a static method |
| Testing | only by clicking | 22 JUnit tests, no window |
| Classes | 3 | 4 (+ 1 test class) |

## The model

The model is a plain Java class. It stores the state, and its operations change the state according to the rules. Its methods fall into two groups:

| Operation | What it does |
| --- | --- |
| `load(Path)` | reads the cards from a file and starts the practice from the beginning |
| `readCards(Path)` | static: parses a file into a list; a malformed line throws `IOException` with its line number |
| `reset()` | first card, question side, zero points – same cards |
| `toggle()` | switches the current card between question and answer |
| `answer(boolean)` | a point for a good answer, then the next card, question first |

| Query | Returns |
| --- | --- |
| `hasCards()` | are any cards loaded |
| `hasCurrentCard()` | is there a card to answer (cards loaded and not all answered) |
| `isFinished()` | all cards answered |
| `getCurrentText()` | the question or the answer of the current card, whichever is showing |
| `getAnsweredCount()`, `getCardCount()`, `getScore()` | the numbers the status line needs |

Name the operations in the language of the *problem* (`answer`, `toggle`, `step`, `newGame`), not of the screen (`buttonClicked`, `onToggle`). The model doesn't know there are buttons.

### One counter, two jobs

There is no "current card index" field. Because the cards are taken in order, the number of answered cards *is* the index of the current card: the current card is always `cards.get(answered)`, and `answered++` is how the practice moves on. When `answered == cards.size()`, there is no current card and the practice is over.

> **Fig. 3** · Three cards, three answers. One integer is both the count and the position – there is no second field that could drift out of sync with it.
>
> *Diagram:* One counter does two jobs: answered is the number of answered cards and also the index of the current card. After three answers on three cards it equals the size of the list, so there is no current card and the practice is finished.

### Guard rails: fail loudly

When there is no current card, `toggle()`, `answer()` and `getCurrentCard()` throw `IllegalStateException` instead of silently doing nothing. A wrong call is a programming error, and you want to see it immediately – in a test, too. The view simply never makes such a call: it disables the buttons that make no sense right now.

```java
public boolean hasCurrentCard() {
    return answered < cards.size();
}

private void requireCurrentCard() {
    if (!hasCurrentCard()) {
        throw new IllegalStateException("There is no current card");
    }
}
```

Good to know

**Two kinds of error**

`IllegalStateException` and `IllegalArgumentException` signal *programmer* mistakes. A *user* mistake – opening a broken file – is a checked `IOException` that the view catches and shows in a dialog.

`readCards` is static: it doesn't depend on the model's state, it turns a file into a list. That makes it testable on its own, and it gives `load` a nice property for free – the old cards are replaced only if reading succeeded:

```java
public void load(Path file) throws IOException {
    load(readCards(file));          // if readCards throws, the old cards stay
}

public void load(List<Card> newCards) {
    cards = new ArrayList<>(newCards);   // the model keeps its own copy
    reset();
}
```

## The view: forward, then refresh

The view builds the window. Its event handlers **only forward** to the model, and after every event the same `refresh()` runs:

```java
toggleButton.addActionListener(e -> {
    model.toggle();
    refresh();
});
```

> **Fig. 4** · The path of one click. The rule runs entirely inside the model; the view's only job afterwards is to ask what is true now and show it.
>
> *Diagram:* Clicking Good answer: the AnswerListener calls model.answer(true), the model adds a point, moves on and shows the next question. Then the listener calls refresh(), which asks the model for the current text, the score and the flags and sets every component from them.

`refresh()` is the **only place** where the view reads the model, and it sets everything from it: the text, the border title (*Question* / *Answer*), the status line and which buttons are enabled.

FlashcardsGUI.refresh() – shortened

```java
private void refresh() {
    if (model.hasCurrentCard()) {
        display.setText(model.getCurrentText());
        displayBorder.setTitle(model.isShowingQuestion() ? "Question" : "Answer");
        statusLabel.setText("Card " + (model.getAnsweredCount() + 1) + " / " + model.getCardCount());
    } else if (model.isFinished()) {
        // ... the result
    } else {
        // ... "open a file"
    }
    resetButton.setEnabled(model.hasCards());
    toggleButton.setEnabled(model.hasCurrentCard());
    goodButton.setEnabled(model.hasCurrentCard());
    wrongButton.setEnabled(model.hasCurrentCard());
}
```

So the window **can never contradict the model**. We don't keep components in sync by hand, button by button; after every event we redraw the window from the state. For a window this size it costs nothing – nor for the few dozen buttons of a board game. (Next session shows what to do when redrawing everything gets wasteful.)

> **Fig. 5** · Scattered updates force every handler to know the whole window. A single refresh() removes that knowledge from the handlers.
>
> *Diagram:* Before: every event handler picks which of the three update methods to call, and toggle calls only one of them. After: every handler ends with the same refresh(), which redraws the whole window from the model.

There is no rule in the view: it doesn't know how many cards there are, it doesn't decide whether a point is due. If an `if` in the view is about the *state* (rather than about how to draw something), it probably belongs in the model.

| Event | Handler | Action |
| --- | --- | --- |
| File ▸ Open… (Ctrl+O) | `this::open` (method reference) | file chooser, `model.load(file)`, error dialog on failure, `refresh()` |
| File ▸ Exit (Ctrl+Q) | lambda | exits |
| Reset button | lambda | `model.reset()`, `refresh()` |
| Toggle Q/A button | lambda | `model.toggle()`, `refresh()` |
| Wrong / Good answer | `AnswerListener(correct)` – inner class | `model.answer(correct)`, `refresh()` |

The open handler: the model reads and validates, the view shows the error

```java
private void open(ActionEvent e) {
    if (fileChooser.showOpenDialog(frame) != JFileChooser.APPROVE_OPTION) {
        return;
    }
    try {
        model.load(fileChooser.getSelectedFile().toPath());
    } catch (IOException ex) {
        showError("Could not load the cards:\n" + ex.getMessage());
    }
    refresh();
}
```

Common mistake

**Storing the state twice**

If the view has its own `int score` next to the model's, sooner or later the two differ. The view keeps components, never a copy of the game. Need the score? Ask `model.getScore()` in `refresh()`.

## Testing the model

Because the model has no Swing in it, its rules can be checked by JUnit without a window. In NetBeans `Alt+F6` runs the tests. The test class has three groups: state with no cards, the flow of a practice (moving on, scoring, finishing, reset) and file reading (blank lines, malformed line with its number, empty and missing file).

```java
@Test
public void answeringAllCardsFinishesThePractice() {
    model.load(List.of(new Card("Q1", "A1"), new Card("Q2", "A2"), new Card("Q3", "A3")));

    model.answer(true);
    model.answer(false);
    model.answer(true);

    assertTrue(model.isFinished());
    assertFalse(model.hasCurrentCard());
    assertEquals(2, model.getScore());
}
```

The guard rail is a rule too – test it

```java
@Test(expected = IllegalStateException.class)
public void cannotAnswerWhenFinished() {
    model.load(List.of(new Card("Q1", "A1")));
    model.answer(true);
    model.answer(true);       // there is nothing left to answer
}
```

File tests write into a temporary folder that JUnit deletes after each test, so they never touch the real `cards.txt`. One last test does read the sample file next to the project – if someone breaks it, the test says so.

```java
@Rule
public TemporaryFolder folder = new TemporaryFolder();   // deleted after each test

@Test
public void readCardsReportsTheMalformedLine() throws IOException {
    Path file = fileWith("Q1~A1\nno separator here\nQ3~A3\n");
    try {
        FlashcardsModel.readCards(file);
        fail("Expected an IOException");
    } catch (IOException ex) {
        assertTrue(ex.getMessage().startsWith("Line 2 "));
    }
}
```

None of these tests could be written for the starting `Flashcards`: its constructor opens a window, and its rules are mixed with component updates.

## The second assignment

The game of the second assignment has the same two layers:

| Flashcards | Board game | What goes in it |
| --- | --- | --- |
| `Card` | `Field`, `Player`, `Position` | small data classes, enums, records |
| `FlashcardsModel` | `Board` (and `Game`) | the board, whose turn it is, the rules of a move, the end of the game, the winner – no Swing |
| `FlashcardsGUI` | `BoardGUI` | button grid, menu, status bar; handlers call the model, then `refresh()` |
| `FlashcardsModelTest` | `BoardTest` | the rules: illegal move, detecting a win, a draw |

In your assignment

**Two columns before the first line of code**

Write down two columns: **state and rules** (model) and **presentation** (view). Every field and method has to fit clearly into one. If it doesn't, rethink it before coding.

The test of a good split: could your `Board` be played from a console program? If the model is "playable" without Swing, the separation worked. Next session's board games are built exactly like this.

In your assignment

**Check before you present**

- No `javax.swing` or `java.awt` import in any model class.
- Every handler is "call the model, then refresh" – no `if` about game state in the view.
- Illegal calls throw (`IllegalStateException`); the view disables or ignores what makes no sense.
- At least one JUnit test per rule of the task.

Good to know

**The next level: observer**

If the model could notify the view about its changes, you wouldn't even have to write the `refresh()` calls by hand – that is the observer pattern from the calculator example. For the assignment, MV with a manual `refresh()` is more than enough.

## Practice

Task 1

**Shuffle the cards**

Add a *Shuffle* button to `FlashcardsMV`: it shuffles the loaded cards and starts the practice over. Put the rule in the model, write a test for it, and keep the handler a two-liner.

**Approach:**

The model gets a `shuffle` operation; the randomness comes from a `Random` parameter so a test can pass a fixed seed. The button handler is `model.shuffle(random); refresh();`.

```java
// in the model – the Random comes from outside, so a test can fix the seed
public void shuffle(Random random) {
    Collections.shuffle(cards, random);
    reset();
}
```

**What to test:**

Not the exact order (that's `Collections.shuffle`'s job) but your rule: the same cards are still there, and the practice restarted.

```java
model.load(cards);
model.shuffle(new Random(42));
// same 3 cards, practice starts over: 0 answered, 0 points, a question showing
```

Task 2

**Practise the wrong ones again**

After the last card, offer *Retry wrong*: a new round with only the cards answered wrong. Which class remembers them? What should happen if there were none?

**Approach:**

The model keeps a second list. `answer(false)` adds the current card *before* moving on; a new operation `retryWrong()` calls `load(wrong)` with a copy. With an empty list, throw `IllegalStateException` – and let `refresh()` disable the button via a query like `hasWrongCards()`.

```java
private final List<Card> wrong = new ArrayList<>();

// in answer(): remember the card BEFORE answered++ moves on
if (!correct) {
    wrong.add(cards.get(answered));
}
```

Task 3

**Tests before features**

Without running the program, write tests for these rules: a wrong answer moves on without a point; toggling twice shows the question again; `reset()` keeps the cards. Run them – they should pass on `FlashcardsMV` as it is.

**Shape of a test:**

Arrange (load three cards with `List.of(new Card(…))`), act (one or two calls), assert (one or two queries). One rule per test, and a name that reads as the rule.

Task 4

**Split your own program**

Take a program you wrote with all its logic in the GUI class (the first assignment, or the Swing exercises). List its fields in two columns, then move the state and the rules into a model class following the five steps.

**Where to start:**

Start with the fields: anything that is not a `JComponent` is a candidate for the model. Then search for every assignment to those fields – each one is part of a rule. When you are done, delete the GUI's `import` lines from the model and see what no longer compiles.

## Summary

- **Model** – the state and the rules of the program, as plain Java – no Swing, no AWT; testable with JUnit
- **View** – builds the window, forwards events to the model, shows the model in refresh()
- **State** – the fields that describe where the program is (cards, score …) – not the components
- **Query** – a model method that only reads: hasCurrentCard(), getScore()
- **Guard** – a check that throws IllegalStateException when an operation makes no sense now

- Find the state (non-component fields) and the rules (code that changes them); both go to the model.
- Handlers forward: call the model, then refresh(). One refresh() reads the model and sets everything.
- The model throws on illegal calls; the view disables what makes no sense.
- No javax.swing or java.awt in the model – then JUnit can test every rule without a window.
- Your board game: Board/Game is the model, BoardGUI is the view, BoardTest tests the rules.
