Separate the calculator’s presentation (View and Controller) from its logic (the Model).

Let’s assume we want to implement a simple pocket calculator described in the previous post in Java. The implementation should follow the classic Model-View-Controller pattern.

TL;DR https://github.com/rmigacz/calculator

MVC components

MVC components - Presentation depends on the Model, the Model doesn’t depend on the Presentation

Let’s see how the main components of the calculator fit into Model-View-Controller parts.

Main Model-View-Controller components of the calculator

Model

The calculator Model represents information about the domain. It holds the calculator’s state and performs calculations. The Model is completely independent of the View and Controller.

View

The calculator View represents the display of the Model: a user interface with buttons and screen.

Controller

The calculator Controller handles user input and updates to the Model; the View updates as a result.

Interaction flow between components

Let’s describe how the components interact with one another using the GoF approach.

MVC flow

MVC flow - time-ordered (reference: based on GoF patterns description)

  1. User performs an action (clicks on the buttons, types, selects).
  2. View translates the User action into a UI event and forwards it to the Controller.
  3. Controller interprets the event and updates the Model.
  4. Model performs actions (e.g., evaluating an operation), changes state (e.g., to RES) and notifies its observers.
  5. View pulls the required state (current value) from the Model.
  6. View updates its internal presentation state.
  7. View re-renders the UI (presents current value).

The Controller does not sit between the View and the Model in terms of dependencies; it sits between them only in the flow of user events (input flow).

Now, let’s describe how these relationships are implemented in this calculator.

Implementation of main relationships - Observer, Composite and Strategy patterns

Observer pattern - Views subscribes to Model notifications

Model-View-Controller decouples views and models by establishing a subscription/notification protocol between them.

Observer pattern class diagram

Observer pattern - class diagram for this calculator

This is achieved by using Observer pattern. Its intent:

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

The one side of the dependency - the Subject interface:

public interface Subject {
    void attach(Observer observer);
    void detach(Observer observer);
    void notifyObservers();
}

The many side of the dependency - the Observer interface:

public interface Observer {
    void update();
}

Model, the concrete Subject:

public class Model implements Subject {
    private final StateMachine stateMachine = new StateMachine();
    private final Actions actions = new Actions();
    private final Context context = new Context();
    private final List<Observer> observers = new ArrayList<>();

    @Override
    public void attach(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        observers.forEach(Observer::update);
    }

    public String getDisplay() {
        return context.getDisplay();
    }

    public void onCommand(Command command) {
        State current = stateMachine.getState();
        actions.apply(command, current, context);
        stateMachine.onCommand(command);
        notifyObservers();
    }

    // test hook
    State getState() {
        return stateMachine.getState();
    }
}

The Model depends only on the Observer interface; when its state changes (onCommand), it notifies all attached observers via notifyObservers().

The View, the concrete Observer:

public class View implements Observer {
    private final Model model;
    private final Screen screen;

    public View(Model model) {
        // UI setup omitted.
        this.model = model;
        this.screen = new Screen();
    }

    @Override
    public void update() {
        screen.setDisplay(model.getDisplay());
    }
}

The View holds a reference to the concrete Model; when the model notifies observers via update(), the view pulls the display state (model.getDisplay()), updates the screen, and re-renders the UI.

Composite pattern - UI elements can be nested

Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

Composite pattern class diagram

Composite pattern - class diagram for this calculator

The calculator’s UI framework already implements the Composite pattern at the framework level. Classes such as Frame and Panel act as composites, while components like Button are leaf nodes.

Strategy pattern - View uses Controller(s) that defines user input strategy

A View uses a Controller to implement a particular user input response strategy.

Strategy pattern class diagram

Strategy pattern - class diagram for this calculator

This is achieved by using Strategy pattern. Its intent:

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

The Controller handles user input Command:

public interface Controller {
    void send(Command command);
}

The concrete Controller - GuiController

// GUI Controller
public class GuiController implements Controller {

    private final Model model;

    public GuiController(Model model) {
        this.model = model;
    }

    @Override
    public void send(Command command) {
        model.onCommand(command);
    }
}

Note that the Controller is directly associated with the Model. It receives user input, determines which Model method to call and invokes that method.

Note that View passes Controller reference to the Keypad:

public class View implements Observer {
    public View(Model model, Controller controller) {
        // UI setup omitted; only wiring shown.
        new Keypad(controller);
    }
}

controller.send(command); is called in buttons action listener:

class Keypad extends Panel {

    Keypad(Controller controller) {
        // Layout and button list omitted.
        add(createButton("7", controller));
    }

    private static Button createButton(Command command, Controller controller) {
        return createButton(command.label(), controller);
    }

    private static Button createButton(String label, Controller controller) {
        Button button = new Button(label);
        button.setActionCommand(label);
        button.addActionListener(e -> {
            Command command = Command.fromLabel(label);
            controller.send(command);
        });
        return button;
    }
}

Summary

First, we described the pocket calculator’s elements using Model-View-Controller concepts. Then we looked at the interaction flow between these Model-View-Controller components. We noticed important things there:

  • the controller does not sit between the view and the model in terms of dependencies
  • the controller handles user inputs and sends them to the model, and the view queries the model for updates

After that we checked how the main relationships were implemented within the code:

  • View(s) - Model communication - Observer pattern
  • View can be nested - Composite pattern
  • Controller-View communication - Strategy pattern.

To learn more about Model-View-Controller pattern, consider starting with these sources (in order):


References: