M8 — Inversion of Control, GUI Design, and Visitor

SENG 365 — Software Engineering

Neil Ernst

University of Victoria

2026-10-21

Module Goals

Design Judgment

By the end of this module, you should be able to:

  • recognize pairwise synchronization dependencies;
  • design Observer/MVC callbacks and choose push or pull data flow;
  • separate GUI component construction, event handling, and domain mutation; and
  • recognize when Visitor’s extensible operations justify its complexity.

Inversion of Control, Observer, and MVC

Preparation

Before class: Read Chapter 8, Sections 8.1–8.4.

Entry Decision

A number appears simultaneously as digits, words, and a slider position. The user may change it from any view.

Which object should know about all the others?

Lucky Number

final class DigitView {
    WordView words;
    SliderView slider;

    void numberChanged(int value) {
        words.show(value);
        slider.show(value);
    }
}

If every view updates every other view, how many dependencies appear as views are added?

Responsibility Split

  • Model: stores and changes the number.
  • Views: render some interpretation of model state.
  • Controllers/handlers: translate user events into model operations.
  • Observers: receive callbacks when relevant model state changes.

MVC is a responsibility decomposition. Observer is one way to keep views synchronized without pairwise dependencies.

Observer/MVC Studio

Produce a diagram for Lucky Number containing:

  • model, two views, and one input handler;
  • observer registration;
  • callback direction;
  • the event that triggers notification; and
  • who owns each state change.

Show both a push and pull option for transferring the new value.

Notification Is a Design Decision

Compare:

void numberChanged(int newValue);  // push
void modelChanged();               // observer pulls

Ask:

  • How much model information does each observer need?
  • Could several fields change atomically?
  • Is the callback interface too broad?
  • What happens when an observer does nothing for an event?
  • When should notification occur: before, during, or after mutation?

Compare Alternatives

  • Which pairwise dependency disappeared?
  • Does the model know concrete view types?
  • Is the source of control clear?
  • Who registers and removes observers?
  • Could a callback cause a second model change?
  • Which event should not trigger notification?

Exit Note

Notify observers when __________, but not when __________.

GUI and Event Handling

Preparation

Before class: Read Chapter 8, Sections 8.5–8.7.

Entry Decision

button.setOnAction(event -> {
    int value = Integer.parseInt(field.getText());
    database.save(value);
    model.setNumber(value);
    label.setText(Integer.toString(value));
});

Which responsibilities belong in the handler? Which should be delegated?

Three Views of a GUI

For Lucky Number, sketch:

  1. the user-visible layout;
  2. the source-code component construction; and
  3. the run-time component object graph.

These are related but not identical. A framework constructs and calls parts of the application according to its lifecycle.

Component and Handler Studio

Produce:

  • a component graph for field, button, slider, and displays;
  • an event-to-handler responsibility map;
  • one handler reduced to translation and delegation; and
  • the model operation it invokes.

Mark framework callbacks: these are places where “something else” controls when your code runs.

Event Handler Boundaries

A handler may reasonably:

  • read event or component data;
  • translate it into domain values;
  • invoke a model/controller operation; and
  • arrange presentation-specific feedback.

It should not quietly become the only home for validation, persistence, and business rules.

Framework Inversion of Control

Application code fills framework-defined extension points:

  • construct or configure components;
  • register function objects as handlers;
  • override selected lifecycle methods; and
  • respond when the framework calls back.

Dependency injection is related but distinct: it answers how collaborators are supplied, not primarily who drives execution.

Exit Note

This event handler should delegate __________ because __________.

Visitor as a Recognition Pattern

Preparation

Before class: Read Chapter 8, Section 8.8.

Entry Decision

An epidemiological tree records each person as a child of the person who infected them.

New operations arrive: count infections, find a person, export, redact, and compute depth. Should every operation become a method on every node type?

The Recursive Structure

Retain the course example:

sealed interface InfectionNode
    permits Person, Cluster { }

record Person(String id) implements InfectionNode { }
record Cluster(List<InfectionNode> children)
    implements InfectionNode { }

Where can traversal and each operation live?

Compare Three Designs

Sketch and compare:

  1. operations as methods on node classes;
  2. external traversal using type checks; and
  3. accept(Visitor) with a visit operation per element type.

Evaluate the cost of:

  • adding a new operation;
  • adding a new node type;
  • early termination during search; and
  • giving the operation access to children.

Visitor’s Tradeoff

Visitor supports an open-ended set of operations without continually changing the element interfaces.

It also:

  • couples visitors to the set of element types;
  • adds double-dispatch ceremony;
  • makes a new element type expensive; and
  • can obscure a simple traversal.

Keep Visitor at recognition level: choose it for the force, not the name.

Solitaire Transfer

The existing Solitaire motivation is the same tradeoff: print, log, search, or remove across a recursive CardSource graph without bloating its interface.

Would a direct method, stream, or external iterator be simpler for the number of operations actually expected?

Exit Note

Visitor is overkill here if __________.