Design patterns
SeniorObserver and Command, implemented the classic Java way — formal interfaces, explicit Invoker/Receiver roles, and how that compares to Python's duck-typed versions.
Created 29 September 2026
2 concepts · detail hidden
01The Observer pattern in Java — a formal `Observer` interface, and how lambdas narrow the ceremony gap
#c-the-observer-pattern-in-java-a-formal-observer-interface-andObserver defines a one-to-many dependency between objects: when a Subject (also called an Observable) changes state, every registered Observer is notified automatically, without the subject needing to know anything concrete about who’s listening. Because Java is nominally typed, “being an observer” has to be declared explicitly through an interface — there’s no duck-typing shortcut the way Python’s plain callables get one for free.
import java.util.ArrayList; import java.util.List; interface Observer { void update(String event); } class Subject { private final List<Observer> observers = new ArrayList<>(); public void subscribe(Observer observer) { observers.add(observer); } public void unsubscribe(Observer observer) { observers.remove(observer); } protected void notifyObservers(String event) { for (Observer observer : observers) { observer.update(event); } } } class TemperatureSensor extends Subject { public void setTemperature(double value) { notifyObservers("temperature changed to " + value); } } class ConsoleLogger implements Observer { @Override public void update(String event) { System.out.println("[LOG] " + event); } } public class Demo { public static void main(String[] args) { TemperatureSensor sensor = new TemperatureSensor(); sensor.subscribe(new ConsoleLogger()); sensor.subscribe(event -> System.out.println("[DISPLAY] " + event)); // lambda satisfies Observer sensor.setTemperature(21.5); // both observers fire } }Observerhere has exactly one abstract method, which makes it a functional interface — since Java 8, anything shaped like that can be satisfied by a lambda or method reference (event -> ...above) instead of a full named class, which meaningfully narrows, without eliminating, the ceremony gap with Python’s “any callable will do.” The JDK actually shipped a built-injava.util.Observer/Observablepair once, but both were deprecated in Java 9 —Observableisn’t an interface (so a class that already extends something else can’t use it), state changes aren’t inherently thread-safe, and the design predates thejava.util.function/lambda-friendly interfaces that make a bespokeObserverinterface like the one above just as easy to write today. For real production event systems needing durability, cross-process delivery, or delivery guarantees a plain in-memory listener list can’t provide, Java reaches for the same category of tool Python does — a message broker (Kafka, RabbitMQ) — rather than scaling up the hand-rolled version; see the Python topic’s OOP, design & patterns subtopic for that same hand-rolled-vs-broker tradeoff and its Python implementation of this pattern.02The Command pattern in Java — `Command` interface, `Receiver`, and an undo-capable `Invoker`
#c-the-command-pattern-in-java-command-interface-receiver-and-aCommand turns a request — “call this method, with these arguments, on that object” — into a standalone object, so it can be handed to other code, queued, logged, or undone instead of disappearing the instant it’s invoked. The roles: the Command interface (
execute(), oftenundo()too), a ConcreteCommand (binds a specific action to the receiver and arguments it needs), the Receiver (the object that actually knows how to do the work), and the Invoker (holds and triggers commands without knowing what any particular one does).import java.util.ArrayDeque; import java.util.Deque; interface Command { void execute(); void undo(); } class Light { // the Receiver — does the real work public void turnOn() { System.out.println("Light on"); } public void turnOff() { System.out.println("Light off"); } } class TurnOnCommand implements Command { // a ConcreteCommand private final Light light; public TurnOnCommand(Light light) { this.light = light; } @Override public void execute() { light.turnOn(); } @Override public void undo() { light.turnOff(); } } class RemoteControl { // the Invoker private final Deque<Command> history = new ArrayDeque<>(); public void submit(Command command) { command.execute(); history.push(command); } public void undoLast() { if (!history.isEmpty()) { history.pop().undo(); } } } public class Demo { public static void main(String[] args) { Light light = new Light(); RemoteControl remote = new RemoteControl(); remote.submit(new TurnOnCommand(light)); // "Light on" remote.undoLast(); // "Light off" } }RemoteControlnever referencesLightorturnOndirectly — it only ever calls.execute()/.undo()on whateverCommandit was handed, which is exactly what lets it queue, log, or replay arbitrary commands uniformly, the same structural benefit Python’s version gets. Command is also where Java’s functional-interface narrowing shows its limit compared to Python: a fire-and-forget command with noundoand no extra state can be replaced by a bareRunnable(() -> light.turnOn()) instead of a named class — but the momentundo()or any held state (here, theLightreference) is needed, a lambda alone has nowhere to put it, and the full class-basedConcreteCommandshown above earns its keep, exactly as in Python’s version of this same pattern.Dequeis used as an explicit undo stack here (push/pop, LIFO) rather than a plainList, mirroring the Python implementation’slist.append/list.pop.