Behavioral Design Patterns
In a well-run office, everyone knows who handles what, how requests travel, and who to tell when something changes. Objects need the same kind of organization. Behavioral patterns are eleven proven ways of deciding which object does which job and how objects talk to each other, so that adding a new rule or a new step doesn't mean rewriting everything.
The Eleven at a Glance
| Pattern | The problem | The idea |
|---|---|---|
| Chain of Responsibility | Several handlers might deal with a request | Pass it along a chain until one handles it |
| Command | Actions must be stored, queued or undone | Turn each action into an object |
| Interpreter | You need to evaluate a small language | Build the sentence out of objects that can evaluate themselves |
| Iterator | Callers need to walk a collection without knowing its insides | Give them an object that hands out one item at a time |
| Mediator | Many objects talking to each other becomes a tangle | Make them all talk through one middle object |
| Memento | You need undo or restore | Save a snapshot of the state, restore it later |
| Observer | Several objects must hear about a change | Let them subscribe; notify all subscribers |
| State | Behavior depends on a mode, with if checks everywhere | Make each mode an object that knows its own behavior |
| Strategy | An algorithm must be swappable | Put each version behind one interface |
| Template Method | Several processes share the same steps with small differences | Fix the steps in a parent class; let subclasses fill in the gaps |
| Visitor | You keep adding new operations to a fixed set of classes | Put each operation in its own visitor class |
Chain of Responsibility
A refund request at a company goes first to the team lead. If the amount is too big for them, it moves to the manager, then to the director. Each person either handles it or passes it on. That line of handlers is a chain of responsibility.
public class Approvals {
abstract static class Approver {
private Approver next;
Approver then(Approver next) {
this.next = next;
return next;
}
String approve(long amount) {
if (canApprove(amount)) {
return name() + " approved Rs. " + amount;
}
return next == null
? "Nobody can approve Rs. " + amount
: next.approve(amount);
}
abstract boolean canApprove(long amount);
abstract String name();
}
static class TeamLead extends Approver {
boolean canApprove(long amount) { return amount <= 5000; }
String name() { return "Team lead"; }
}
static class Manager extends Approver {
boolean canApprove(long amount) { return amount <= 50000; }
String name() { return "Manager"; }
}
static class Director extends Approver {
boolean canApprove(long amount) { return amount <= 500000; }
String name() { return "Director"; }
}
public static void main(String[] args) {
Approver chain = new TeamLead();
chain.then(new Manager()).then(new Director());
for (long amount : new long[] {3000, 20000, 200000, 900000}) {
System.out.println(chain.approve(amount));
}
}
}Team lead approved Rs. 3000
Manager approved Rs. 20000
Director approved Rs. 200000
Nobody can approve Rs. 900000The code that asks for approval only talks to the first link. Adding a CFO for bigger amounts means adding one class to the end of the chain. Servlet filters in web apps work the same way.
Command
In a restaurant, the waiter doesn't cook. They write your order on a slip and hand it to the kitchen. The slip is the request turned into a thing that can be queued, passed around, or canceled. Command turns an action into an object, which makes undo easy.
import java.util.ArrayDeque;
import java.util.Deque;
public class Commands {
interface Command {
void execute();
void undo();
}
static class Lamp {
int brightness = 0;
}
static class Brighter implements Command {
private final Lamp lamp;
Brighter(Lamp lamp) { this.lamp = lamp; }
public void execute() { lamp.brightness += 20; }
public void undo() { lamp.brightness -= 20; }
}
static class Remote {
private final Deque<Command> history = new ArrayDeque<>();
void press(Command c) {
c.execute();
history.push(c);
}
void undo() {
if (!history.isEmpty()) {
history.pop().undo();
}
}
}
public static void main(String[] args) {
Lamp lamp = new Lamp();
Remote remote = new Remote();
remote.press(new Brighter(lamp));
remote.press(new Brighter(lamp));
System.out.println("Brightness: " + lamp.brightness);
remote.undo();
System.out.println("After undo: " + lamp.brightness);
}
}Brightness: 40
After undo: 20Because every button press is an object, the remote can keep a history of them and undo the last one. Runnable tasks given to threads are commands too: an action wrapped in an object, to be run later.
Interpreter
A calculator app has to understand text like "50 + 30 - 15". Interpreter represents each piece of such a small language as an object (a number, an addition, a subtraction), and lets the whole sentence evaluate itself.
public class Calculator {
interface Expr { int eval(); }
record Num(int value) implements Expr {
public int eval() { return value; }
}
record Plus(Expr left, Expr right) implements Expr {
public int eval() { return left.eval() + right.eval(); }
}
record Minus(Expr left, Expr right) implements Expr {
public int eval() { return left.eval() - right.eval(); }
}
static Expr parse(String text) {
String[] parts = text.split(" ");
Expr result = new Num(Integer.parseInt(parts[0]));
for (int i = 1; i < parts.length; i += 2) {
Expr next = new Num(Integer.parseInt(parts[i + 1]));
result = parts[i].equals("+")
? new Plus(result, next)
: new Minus(result, next);
}
return result;
}
public static void main(String[] args) {
Expr e = parse("50 + 30 - 15");
System.out.println(e.eval());
}
}65parse turns the text into a small tree of objects, Minus(Plus(50, 30), 15), and calling eval() on the top makes each piece work out its own value. Java's regular expressions use this idea internally: a pattern string is turned into a tree of matching objects.
Iterator
When you read a book, you turn pages one at a time without caring how the book was bound. An iterator gives you one item at a time from a collection, without showing how the collection stores them. Every Java collection has one, and the for-each loop uses it behind the scenes.
import java.util.Iterator;
public class Countdown implements Iterable<Integer> {
private final int from;
Countdown(int from) { this.from = from; }
public Iterator<Integer> iterator() {
return new Iterator<>() {
private int current = from;
public boolean hasNext() { return current > 0; }
public Integer next() { return current--; }
};
}
public static void main(String[] args) {
for (int n : new Countdown(5)) {
System.out.print(n + " ");
}
System.out.println("Go!");
}
}5 4 3 2 1 Go!Countdown doesn't store any list at all; it makes up the numbers as they're asked for. Because it implements Iterable, the for-each loop works on it just like on an ArrayList.
Mediator
Planes near an airport don't radio each other to decide who lands first; they all talk to the control tower. A mediator is that tower: objects send messages through one central object instead of keeping track of everyone else.
import java.util.ArrayList;
import java.util.List;
public class ChatRoom {
private final List<Member> members = new ArrayList<>();
void join(Member m) {
members.add(m);
m.room = this;
}
void send(Member from, String text) {
for (Member m : members) {
if (m != from) {
m.receive(from.name, text);
}
}
}
static class Member {
final String name;
ChatRoom room;
Member(String name) { this.name = name; }
void say(String text) { room.send(this, text); }
void receive(String from, String text) {
System.out.println(name + " got from " + from + ": " + text);
}
}
public static void main(String[] args) {
ChatRoom room = new ChatRoom();
Member navika = new Member("Navika");
room.join(navika);
room.join(new Member("Srihitha"));
room.join(new Member("Advitha"));
navika.say("Study group at 5?");
}
}Srihitha got from Navika: Study group at 5?
Advitha got from Navika: Study group at 5?No member keeps a list of the others. Each one only knows the room, so adding a member, or a rule such as "mute this person", happens in one place.
Memento
A video game lets you save before a hard level and load that save if things go wrong. A memento is that save point: a snapshot of an object's state that can be stored and later used to restore it, without exposing the object's insides.
public class Notes {
record Snapshot(String text) { }
static class Editor {
private String text = "";
void type(String words) { text += words; }
String text() { return text; }
Snapshot save() { return new Snapshot(text); }
void restore(Snapshot s) { text = s.text(); }
}
public static void main(String[] args) {
Editor editor = new Editor();
editor.type("Dear Advitha,");
Snapshot saved = editor.save();
editor.type(" asdf qwerty");
System.out.println(editor.text());
editor.restore(saved);
System.out.println(editor.text());
}
}Dear Advitha, asdf qwerty
Dear Advitha,The editor creates and reads its own snapshots; nobody else edits the saved text. Keeping a list of snapshots gives you full undo history.
Observer
When a channel you follow posts a new video, you get a notification, along with everyone else who subscribed. Observer lets objects subscribe to another object and be told whenever it changes. The full lesson, with subscribing, unsubscribing and memory pitfalls, is "Observer Pattern in Java".
import java.util.ArrayList;
import java.util.List;
public class ObserverDemo {
interface Subscriber { void notify(String video); }
static class Channel {
private final List<Subscriber> subscribers = new ArrayList<>();
void subscribe(Subscriber s) { subscribers.add(s); }
void upload(String video) {
for (Subscriber s : subscribers) {
s.notify(video);
}
}
}
public static void main(String[] args) {
Channel channel = new Channel();
channel.subscribe(v -> System.out.println("Navika sees: " + v));
channel.subscribe(v -> System.out.println("Srihitha sees: " + v));
channel.upload("Java in 10 minutes");
}
}Navika sees: Java in 10 minutes
Srihitha sees: Java in 10 minutesState
A traffic light behaves differently depending on its color: on red you wait, on green you go. Instead of if (color == RED) ... else if ... scattered everywhere, State puts each color's behavior in its own object, and the light simply moves from one state object to the next.
public class TrafficSignal {
enum Light {
RED {
Light next() { return GREEN; }
boolean canCross() { return false; }
},
GREEN {
Light next() { return YELLOW; }
boolean canCross() { return true; }
},
YELLOW {
Light next() { return RED; }
boolean canCross() { return false; }
};
abstract Light next();
abstract boolean canCross();
}
private Light state = Light.RED;
void tick() { state = state.next(); }
String status() {
return state + (state.canCross() ? ": cross now" : ": wait");
}
public static void main(String[] args) {
TrafficSignal signal = new TrafficSignal();
for (int i = 0; i < 4; i++) {
System.out.println(signal.status());
signal.tick();
}
}
}RED: wait
GREEN: cross now
YELLOW: wait
RED: waitEach state knows two things: what comes next, and whether people can cross. TrafficSignal has no if about colors at all. Adding a flashing mode means adding one constant. Order flows (placed, packed, shipped, delivered) are often written this way.
Strategy
At a shop counter you can pay by UPI, card or cash; the counter doesn't change, only the method does. Strategy puts each version of an algorithm behind one interface, so you can swap them, even while the program runs. See "Strategy Pattern in Java" for the full lesson.
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class StrategyDemo {
public static void main(String[] args) {
List<String> names =
new ArrayList<>(List.of("Srihitha", "Navika", "Advitha"));
names.sort(Comparator.naturalOrder());
System.out.println(names);
names.sort(Comparator.comparing(String::length));
System.out.println(names);
}
}[Advitha, Navika, Srihitha]
[Navika, Advitha, Srihitha]sort is the same both times; only the comparison strategy changes, first alphabetical, then by length.
Template Method
Every hot drink is made the same way: boil water, brew, pour into a cup, then add extras. Only the brewing and the extras change between tea and coffee. Template Method writes the fixed recipe once in a parent class and lets subclasses fill in the steps that differ.
public class HotDrinks {
abstract static class HotDrink {
final void make() {
System.out.println("Boil water");
brew();
System.out.println("Pour into cup");
extras();
}
abstract void brew();
void extras() { }
}
static class Tea extends HotDrink {
void brew() { System.out.println("Steep tea leaves"); }
void extras() { System.out.println("Add ginger"); }
}
static class Coffee extends HotDrink {
void brew() { System.out.println("Add coffee powder"); }
}
public static void main(String[] args) {
new Tea().make();
System.out.println("---");
new Coffee().make();
}
}Boil water
Steep tea leaves
Pour into cup
Add ginger
---
Boil water
Add coffee powder
Pour into cupmake() is final, so the order of steps can't be broken. brew() must be filled in; extras() is optional, with an empty default. "Abstract Classes and Abstract Methods" uses the same idea for payslips.
Visitor
A tax inspector visits a grocery, a jeweller and a restaurant, applying a different rule in each. The shops don't change to support inspections; the inspector carries the rules. Visitor puts a new operation in its own class that "visits" each kind of object, so you can add operations without touching the classes being visited.
import java.util.List;
public class VisitorDemo {
interface ShapeVisitor {
String visitCircle(Circle c);
String visitRect(Rect r);
}
interface Shape { String accept(ShapeVisitor v); }
record Circle(double r) implements Shape {
public String accept(ShapeVisitor v) { return v.visitCircle(this); }
}
record Rect(double w, double h) implements Shape {
public String accept(ShapeVisitor v) { return v.visitRect(this); }
}
static class AreaVisitor implements ShapeVisitor {
public String visitCircle(Circle c) {
return String.format("circle area %.2f", Math.PI * c.r() * c.r());
}
public String visitRect(Rect r) {
return String.format("rectangle area %.2f", r.w() * r.h());
}
}
static class PerimeterVisitor implements ShapeVisitor {
public String visitCircle(Circle c) {
return String.format("circle perimeter %.2f", 2 * Math.PI * c.r());
}
public String visitRect(Rect r) {
double p = 2 * (r.w() + r.h());
return String.format("rectangle perimeter %.2f", p);
}
}
public static void main(String[] args) {
List<Shape> shapes = List.of(new Circle(1), new Rect(2, 3));
for (Shape s : shapes) {
System.out.println(s.accept(new AreaVisitor()));
System.out.println(s.accept(new PerimeterVisitor()));
}
}
}circle area 3.14
circle perimeter 6.28
rectangle area 6.00
rectangle perimeter 10.00Adding a new operation, say "cost to paint", means one new visitor class; Circle and Rect don't change. In modern Java, a sealed interface with a switch over its records often does the same job with less code (see "Sealed Classes and Interfaces in Java").
Common Mistakes
Mixing up State and Strategy. Both swap an object. In Strategy, the caller chooses the algorithm; in State, the object changes its own state as things happen.
Chains with no end. In Chain of Responsibility, always decide what happens if no handler takes the request.
Using Visitor for classes that change often. Every new class means updating every visitor.
Interview Corner
What are the behavioral design patterns?
They deal with how objects share responsibilities and communicate. Chain of Responsibility passes a request along handlers; Command wraps an action in an object; Interpreter evaluates a small language; Iterator walks a collection; Mediator centralizes communication; Memento saves and restores state; Observer notifies subscribers; State changes behavior with internal state; Strategy swaps algorithms; Template Method fixes the steps of an algorithm; and Visitor adds operations to a class hierarchy without changing it.
What is the difference between State and Strategy?
Both delegate behavior to an object behind an interface. In Strategy, the client picks the algorithm, and it usually doesn't change during the work. In State, the context moves between state objects by itself as events happen, and each state knows which state comes next.
Practice
- Easy: Add a
CFOapprover for amounts up to Rs. 20,00,000 toApprovals.javaand check that the 900000 request is now approved. - Harder: Add a
Dimmercommand toCommands.javathat lowers brightness by 10, and show that pressing Brighter, Dimmer, then undo twice brings the lamp back to 0.
Key Takeaways
Was this tutorial helpful?