← All categories

Behavioral

Who is responsible for what, and how objects talk to each other.

Behavioral problems show up as long conditionals, classes that call everyone directly, and logic that has to be repeated whenever something new is added. The code works, but each new case means opening the same method again.

The refactorings here turn actions, rules and states into objects of their own. Once a rule is an object, it can be swapped, queued, undone, chained or told about events, without the code that uses it changing.

You'll recognise it when

  • A method grows a new else-if for every new case.
  • One class calls five others directly whenever something happens.
  • Behaviour depends on a status field checked in many methods.
  • Undo, retry or logging is bolted on with a switch over action names.

Example

A smart-home app runs scenes such as "Movie night" and must be able to undo them.

Before
void run(String scene) {
    switch (scene) {
        case "movie" -> { lights.dim(20); blinds.close(); tv.on(); }
        case "away"  -> { lights.off(); thermostat.set(16); }
    }
    history.add(scene);
}

void undo() {
    String last = history.removeLast();
    switch (last) {   // must mirror run() exactly, by hand
        case "movie" -> { lights.dim(100); blinds.open(); tv.off(); }
        case "away"  -> { lights.on(); thermostat.set(21); }
    }
}
After
interface SceneAction {
    void apply();
    void revert();
}

class MovieNight implements SceneAction {
    public void apply()  { lights.dim(20);  blinds.close(); tv.on(); }
    public void revert() { lights.dim(100); blinds.open();  tv.off(); }
}

void run(SceneAction action) {
    action.apply();
    history.push(action);
}

void undo() { history.pop().revert(); }

Why it's better: Each scene keeps its "do" and "undo" side by side, so they can't drift apart, and adding a scene doesn't touch run() or undo().

Ideas you'll practise

StrategyObserverStateCommandChain of ResponsibilityTemplate MethodIteratorMediatorMementoVisitor

Problems 29