Virgil-Nicolae Șerbănuță
2025
Reusable solutions to common software design problems.
Popularized by the “Gang of Four” (Gamma, Helm, Johnson, Vlissides, 1994).
Instead of reinventing how to traverse a collection, we apply the Iterator pattern.
Design patterns are typically grouped into three main categories:
| Category | Description | Example Patterns |
|---|---|---|
| Creational | How objects are created | Singleton, Builder, Factory Method |
| Structural | How classes and objects are composed | Adapter, Bridge, Decorator |
| Behavioral | How objects interact and communicate | Iterator, Observer, State |
Behavioral pattern
Provide a way to access elements of a collection sequentially without exposing its internal structure.
How to traverse a collection (e.g., list, tree, array) without knowing its implementation?
Define an Iterator interface with methods like
hasNext() and next().
// Step 1: Create the Iterator interface
interface Iterator {
boolean hasNext();
Object next();
}
// Step 2: Create the Container interface
interface Container {
Iterator getIterator();
}
// Step 3: Create a concrete class implementing Container
class NameRepository implements Container {
private String[] names = {"Alice", "Bob", "Charlie", "Diana"};
@Override
public Iterator getIterator() {
return new NameIterator();
}
// Inner class implementing the Iterator interface
private class NameIterator implements Iterator {
int index;
@Override
public boolean hasNext() {
return index < names.length;
}
@Override
public Object next() {
if (this.hasNext()) {
return names[index++];
}
return null;
}
}
}
// Step 4: Use the iterator
public class IteratorPatternDemo {
public static void main(String[] args) {
NameRepository namesRepo = new NameRepository();
for (Iterator iter = namesRepo.getIterator(); iter.hasNext();) {
String name = (String) iter.next();
System.out.println("Name: " + name);
}
}
}Modify 3 lines in the example above to make the iterator a reverse iterator.
How does this custom Iterator differ from Java’s built-in
java.util.Iterator, and when might you still implement your own?
Creational pattern
Separate the construction of a complex object from its representation, so the same construction process can create different representations.
How can we construct complex objects step by step while keeping the construction logic separate from the representation?
Use a Builder class to encapsulate object creation in multiple steps.
Design a Computer class that represents a configurable computer system. The goal is to let users “build” a computer step-by-step, choosing which components to include. A computer may include:
CPU (e.g., “Intel i9”, “AMD Ryzen 7”)
GPU (e.g., “NVIDIA RTX 4090”, “AMD Radeon RX 7800”)
RAM (in GB)
Storage (in GB)
You should be able to build objects fluently, like this:
// Product class
class Computer {
private String CPU;
private String GPU;
private int RAM;
private int storage;
// Private constructor — use Builder instead
private Computer(Builder builder) {
this.CPU = builder.CPU;
this.GPU = builder.GPU;
this.RAM = builder.RAM;
this.storage = builder.storage;
}
@Override
public String toString() {
return "Computer [CPU=" + CPU + ", GPU=" + GPU + ", RAM=" + RAM + "GB, Storage=" + storage + "GB]";
}
// Static nested Builder class
public static class Builder {
private String CPU;
private String GPU;
private int RAM;
private int storage;
public Builder setCPU(String CPU) {
this.CPU = CPU;
return this;
}
public Builder setGPU(String GPU) {
this.GPU = GPU;
return this;
}
public Builder setRAM(int RAM) {
this.RAM = RAM;
return this;
}
public Builder setStorage(int storage) {
this.storage = storage;
return this;
}
public Computer build() {
return new Computer(this);
}
}
}
// Demo class
public class BuilderPatternDemo {
public static void main(String[] args) {
Computer gamingPC = new Computer.Builder()
.setCPU("Intel i9")
.setGPU("NVIDIA RTX 4090")
.setRAM(32)
.setStorage(2000)
.build();
System.out.println(gamingPC);
}
}Design a Pizza class that represents a customizable pizza order, using the Builder Pattern. Your pizza should have:
A size (e.g., Small, Medium, Large)
A crust type (e.g., Thin, Thick, Stuffed)
A list of toppings (e.g., Cheese, Pepperoni, Mushrooms)
A flag for extra cheese
The goal is to make object creation flexible and readable, like this:
Creational pattern
Ensure a class has only one instance, and provide a global point of access to it.
How can we make sure there is exactly one instance of a class used throughout a system?
Implement a DatabaseConnection class that simulates a
single, shared connection to a database.
The program should ensure that:
Only one instance of the connection is ever created.
Any part of the program that requests a connection gets the same instance.
// Singleton class
class DatabaseConnection {
// Step 1: Create a private static instance of the class
private static DatabaseConnection instance;
// Step 2: Make the constructor private to prevent instantiation
private DatabaseConnection() {
System.out.println("Connecting to the database...");
}
// Step 3: Provide a public static method to get the single instance
public static DatabaseConnection getInstance() {
if (instance == null) {
instance = new DatabaseConnection();
}
return instance;
}
// Example method
public void query(String sql) {
System.out.println("Executing query: " + sql);
}
}
// Demo class
public class SingletonDemo {
public static void main(String[] args) {
DatabaseConnection conn1 = DatabaseConnection.getInstance();
DatabaseConnection conn2 = DatabaseConnection.getInstance();
conn1.query("SELECT * FROM users");
// Show that both references point to the same object
System.out.println(conn1 == conn2); // true
}
}Implement a Logger for a simple application. Only one
instance of this class should ever exist, and all parts of the program
should share it.
Logger logger1 = Logger.getInstance();
Logger logger2 = Logger.getInstance();
logger1.log("Starting the app...");
logger2.log("App is running.");
// Both should refer to the same instance
System.out.println(logger1 == logger2); // trueLogger initialized.
[LOG]: Starting the app...
[LOG]: App is running.
true
Behavioral pattern
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
How can objects stay in sync without tight coupling between them?
Introduce a Subject that maintains a list of Observers; when the Subject changes, it notifies all Observers.
Imagine we are building a Weather Station system that needs to update several displays (temperature, humidity, forecast) whenever new weather data is available.
Your task is to design the update mechanism between the Weather Station (subject) and its displays (observers).
import java.util.*;
interface Observer {
void update(float temperature);
}
interface Subject {
void attach(Observer o);
void detach(Observer o);
void notifyObservers();
}
class WeatherStation implements Subject {
private List<Observer> observers = new ArrayList<>();
private float temperature;
public void attach(Observer o) {
observers.add(o);
}
public void detach(Observer o) {
observers.remove(o);
}
public void setTemperature(float t) {
this.temperature = t;
notifyObservers();
}
public void notifyObservers() {
for (Observer o : observers) {
o.update(temperature);
}
}
}
class Display implements Observer {
private String name;
public Display(String name) {
this.name = name;
}
public void update(float temperature) {
System.out.println(name + " display: new temperature = " + temperature);
}
}
public class Main {
public static void main(String[] args) {
WeatherStation station = new WeatherStation();
Display d1 = new Display("Main");
Display d2 = new Display("Secondary");
station.attach(d1);
station.attach(d2);
station.setTemperature(25.0f);
station.setTemperature(30.0f);
}
}Create a simplified Chat Room system. The ChatRoom (the
Subject) notifies all registered Users (the
Observers) whenever a new message is sent.
[Alice] says: Hey everyone!
[Notification to Bob] New message in Design Patterns: Alice says “Hey everyone!”
[Notification to Charlie] New message in Design Patterns: Alice says “Hey everyone!”
[Bob] says: Hi Alice!
[Notification to Alice] New message in Design Patterns: Bob says “Hi Alice!”
[Notification to Charlie] New message in Design Patterns: Bob says “Hi Alice!”
Creational pattern
Define an interface for creating an object, but let subclasses decide which class to instantiate.
How can we delegate the instantiation of related objects without hardcoding specific classes?
Create a factory interface that defines a method for object creation, implemented by concrete subclasses.
We need to build a system for creating different types of documents (e.g., PDF, Word). Each document has its own creation process, but they share a common interface.
Provide a flexible design to handle this.
abstract class Document {
public abstract void open();
}
class PDFDocument extends Document {
public void open() {
System.out.println("Opening PDF Document");
}
}
class WordDocument extends Document {
public void open() {
System.out.println("Opening Word Document");
}
}
abstract class Application {
public abstract Document createDocument();
public void newDocument() {
Document doc = createDocument();
doc.open();
}
}
class PDFApp extends Application {
public Document createDocument() {
return new PDFDocument();
}
}
class WordApp extends Application {
public Document createDocument() {
return new WordDocument();
}
}
public class FactoryDemo {
public static void main(String[] args) {
Application pdfApp = new PDFApp();
pdfApp.newDocument();
Application wordApp = new WordApp();
wordApp.newDocument();
}
}Implement a Shape Factory that can create different kinds of shapes — such as Circle, Rectangle, and Square — based on a given input. The main program should be able to request shapes without knowing their exact classes.
ShapeFactory factory = new ShapeFactory();
Shape shape1 = factory.getShape("CIRCLE");
shape1.draw();
Shape shape2 = factory.getShape("RECTANGLE");
shape2.draw();Drawing a Circle
Drawing a Rectangle
Behavioral pattern
Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
How can we manage an object’s behavior that depends on its current state, without long if/else or switch statements?
Encapsulate state-specific behavior into separate classes and delegate state handling to them.
We’re building a Media Player that behaves differently depending on whether it’s in the Playing, Paused, or Stopped state.
Design the system so that transitions between states change the behavior dynamically.
interface State {
void play(Player player);
void pause(Player player);
void stop(Player player);
}
class PlayingState implements State {
public void play(Player player) {
System.out.println("Already playing");
}
public void pause(Player player) {
System.out.println("Pausing playback");
player.setState(new PausedState());
}
public void stop(Player player) {
System.out.println("Stopping playback");
player.setState(new StoppedState());
}
}
class PausedState implements State {
public void play(Player player) {
System.out.println("Resuming playback");
player.setState(new PlayingState());
}
public void pause(Player player) {
System.out.println("Already paused");
}
public void stop(Player player) {
System.out.println("Stopping from paused state");
player.setState(new StoppedState());
}
}
class StoppedState implements State {
public void play(Player player) {
System.out.println("Starting playback");
player.setState(new PlayingState());
}
public void pause(Player player) {
System.out.println("Can't pause, player is stopped");
}
public void stop(Player player) {
System.out.println("Already stopped");
}
}
class Player {
private State state;
public Player() {
this.state = new StoppedState();
}
public void setState(State state) {
this.state = state;
}
public void play() {
state.play(this);
}
public void pause() {
state.pause(this);
}
public void stop() {
state.stop(this);
}
}
public class StateDemo {
public static void main(String[] args) {
Player player = new Player();
player.play();
player.pause();
player.stop();
}
}Implement a Vending Machine that sells a single type of item (e.g., a soda). The machine’s behavior depends on its current state:
When no coin is inserted, you can’t buy anything.
When a coin is inserted, you can choose to dispense the item or cancel.
When dispensing, it delivers the item and returns to waiting for a new coin.
The vending machine will have three states:
NoCoinState – waiting for a coin
HasCoinState – coin inserted, waiting for user to select item
DispensingState – vending the item
VendingMachine machine = new VendingMachine();
machine.insertCoin();
machine.pressButton();
machine.insertCoin();
machine.returnCoin();
machine.pressButton();Coin inserted.
Dispensing item...
Please take your item.
Coin inserted.
Coin returned.
Insert coin first.
Design patterns capture proven design experience.
Iterator decouples collection traversal from representation
Builder separates the construction of an object from representation
Singleton ensures a class has only one, globally accessible, instance
Observer decouples state change notification.
Factory Method delegates object creation to subclasses or factories.
State encapsulates behavior changes without complex conditionals.