Comprehensive Guide to Java Design Patterns with Implementation Examples

Creational Patterns

Singleton Pattern

The Singleton pattern ensures that a class has only one instance and provides a global access point to it. This pattern is useful for managing shared resources like database connections, configuration settings, or logging services.

Benefits and Drawbacks

Benefits include controlled access to the sole instance, reduced memory footprint, and deferred initialization. However, singletons can introduce global state, making testing difficult and creating hidden dependencies between classes.

Implementation Approaches

Eager Initialization:

public class ConfigurationManager {
    private static final ConfigurationManager INSTANCE = new ConfigurationManager();
    
    private ConfigurationManager() {}
    
    public static ConfigurationManager getInstance() {
        return INSTANCE;
    }
    
    public void loadSettings() {
        System.out.println("Loading configuration settings...");
    }
}

Lazy Initialization with Double-Checked Locking:

public class DatabaseConnector {
    private static volatile DatabaseConnector connector;
    
    private DatabaseConnector() {}
    
    public static DatabaseConnector getInstance() {
        if (connector == null) {
            synchronized (DatabaseConnector.class) {
                if (connector == null) {
                    connector = new DatabaseConnector();
                }
            }
        }
        return connector;
    }
    
    public void connect() {
        System.out.println("Establishing database connection...");
    }
}

Static Inner Class (Recommended):

public class LoggerService {
    private LoggerService() {}
    
    private static class LoggerHolder {
        private static final LoggerService INSTANCE = new LoggerService();
    }
    
    public static LoggerService getInstance() {
        return LoggerHolder.INSTANCE;
    }
    
    public void log(String message) {
        System.out.println("[LOG] " + message);
    }
}

Factory Method Pattern

The Factory Method pattern defines an interface for creating objects but lets subclasses decide which class to instantiate. This promotes loose coupling by separating object creation from usage.

public interface Transport {
    void deliver();
}

public class Truck implements Transport {
    @Override
    public void deliver() {
        System.out.println("Delivering by land with truck");
    }
}

public class Ship implements Transport {
    @Override
    public void deliver() {
        System.out.println("Delivering by sea with ship");
    }
}

public abstract class LogisticsCenter {
    public abstract Transport createTransport();
    
    public void planDelivery() {
        Transport transport = createTransport();
        transport.deliver();
    }
}

public class RoadLogistics extends LogisticsCenter {
    @Override
    public Transport createTransport() {
        return new Truck();
    }
}

public class SeaLogistics extends LogisticsCenter {
    @Override
    public Transport createTransport() {
        return new Ship();
    }
}

Abstract Factory Pattern

Abstract Factory provides an interface for creating families of related objects without specifying their concrete classes. This is useful when systems need to be independent of how their products are created.

public interface Button {
    void render();
}

public interface Checkbox {
    void render();
}

public class WindowsButton implements Button {
    @Override
    public void render() {
        System.out.println("Rendering Windows-style button");
    }
}

public class MacOSButton implements Button {
    @Override
    public void render() {
        System.out.println("Rendering macOS-style button");
    }
}

public class WindowsCheckbox implements Checkbox {
    @Override
    public void render() {
        System.out.println("Rendering Windows-style checkbox");
    }
}

public class MacOSCheckbox implements Checkbox {
    @Override
    public void render() {
        System.out.println("Rendering macOS-style checkbox");
    }
}

public interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

public class WindowsFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new WindowsButton();
    }
    
    @Override
    public Checkbox createCheckbox() {
        return new WindowsCheckbox();
    }
}

public class MacOSFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new MacOSButton();
    }
    
    @Override
    public Checkbox createCheckbox() {
        return new MacOSCheckbox();
    }
}

Prototype Pattern

Prototype pattern creates new objects by cloning existing ones instead of creating from scratch. This is beneficial when object creation is expensive or complex.

public abstract class Document implements Cloneable {
    protected String content;
    protected String formatting;
    
    public abstract void print();
    
    @Override
    public Document clone() {
        try {
            return (Document) super.clone();
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }
}

public class ReportDocument extends Document {
    public ReportDocument() {
        this.content = "Standard Report Template";
        this.formatting = "Professional Layout";
    }
    
    @Override
    public void print() {
        System.out.println("Report: " + content + " [" + formatting + "]");
    }
}

public class InvoiceDocument extends Document {
    public InvoiceDocument() {
        this.content = "Invoice Template";
        this.formatting = "Financial Format";
    }
    
    @Override
    public void print() {
        System.out.println("Invoice: " + content + " [" + formatting + "]");
    }
}

Builder Pattern

Builder pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations.

public class Computer {
    private final String cpu;
    private final String ram;
    private final String storage;
    private final String gpu;
    
    private Computer(Builder builder) {
        this.cpu = builder.cpu;
        this.ram = builder.ram;
        this.storage = builder.storage;
        this.gpu = builder.gpu;
    }
    
    public static class Builder {
        private String cpu;
        private String ram;
        private String storage;
        private String gpu;
        
        public Builder setCpu(String cpu) {
            this.cpu = cpu;
            return this;
        }
        
        public Builder setRam(String ram) {
            this.ram = ram;
            return this;
        }
        
        public Builder setStorage(String storage) {
            this.storage = storage;
            return this;
        }
        
        public Builder setGpu(String gpu) {
            this.gpu = gpu;
            return this;
        }
        
        public Computer build() {
            return new Computer(this);
        }
    }
    
    public void displaySpecs() {
        System.out.println("CPU: " + cpu + ", RAM: " + ram + ", Storage: " + storage + ", GPU: " + gpu);
    }
}

Structural Patterns

Proxy Pattern

Proxy pattern provides a surrogate or placeholder for another object to control access to it. Common uses include lazy loading, access control, and logging.

public interface DataService {
    void fetchData(String query);
}

public class RealDataService implements DataService {
    @Override
    public void fetchData(String query) {
        System.out.println("Executing query: " + query);
    }
}

public class DataServiceProxy implements DataService {
    private RealDataService realService;
    private final String userRole;
    
    public DataServiceProxy(String userRole) {
        this.userRole = userRole;
    }
    
    @Override
    public void fetchData(String query) {
        if (userRole.equals("ADMIN")) {
            if (realService == null) {
                realService = new RealDataService();
            }
            System.out.println("[LOG] Access granted for role: " + userRole);
            realService.fetchData(query);
        } else {
            System.out.println("Access denied. Admin role required.");
        }
    }
}

Adapter Pattern

Adapter pattern allows incompatible interfaces to work together by wrapping an existing class with a new interface.

public interface PaymentProcessor {
    void processPayment(double amount);
}

public class PayPalGateway {
    public void makePayment(double total) {
        System.out.println("Processing payment via PayPal: $" + total);
    }
}

public class StripeGateway {
    public void charge(double amountInCents) {
        System.out.println("Charging via Stripe: " + amountInCents + " cents");
    }
}

public class PayPalAdapter implements PaymentProcessor {
    private PayPalGateway payPal;
    
    public PayPalAdapter() {
        this.payPal = new PayPalGateway();
    }
    
    @Override
    public void processPayment(double amount) {
        payPal.makePayment(amount);
    }
}

public class StripeAdapter implements PaymentProcessor {
    private StripeGateway stripe;
    
    public StripeAdapter() {
        this.stripe = new StripeGateway();
    }
    
    @Override
    public void processPayment(double amount) {
        stripe.charge(amount * 100);
    }
}

Decorator Pattern

Decorator pattern attaches additional responsibilities to an object dynamically without altering its structure.

public interface Message {
    String getContent();
}

public class SimpleMessage implements Message {
    private String text;
    
    public SimpleMessage(String text) {
        this.text = text;
    }
    
    @Override
    public String getContent() {
        return text;
    }
}

public abstract class MessageDecorator implements Message {
    protected Message wrappedMessage;
    
    public MessageDecorator(Message message) {
        this.wrappedMessage = message;
    }
}

public class EncryptedMessage extends MessageDecorator {
    public EncryptedMessage(Message message) {
        super(message);
    }
    
    @Override
    public String getContent() {
        return "[ENCRYPTED] " + wrappedMessage.getContent();
    }
}

public class CompressedMessage extends MessageDecorator {
    public CompressedMessage(Message message) {
        super(message);
    }
    
    @Override
    public String getContent() {
        return "[COMPRESSED] " + wrappedMessage.getContent();
    }
}

Bridge Pattern

Bridge pattern decouples abstraction from implementation so that both can vary independently.

public interface Renderer {
    void renderCircle(int x, int y, int radius);
}

public class VectorRenderer implements Renderer {
    @Override
    public void renderCircle(int x, int y, int radius) {
        System.out.println("Drawing vector circle at (" + x + "," + y + ") radius " + radius);
    }
}

public class RasterRenderer implements Renderer {
    @Override
    public void renderCircle(int x, int y, int radius) {
        System.out.println("Drawing raster circle at (" + x + "," + y + ") radius " + radius);
    }
}

public abstract class Shape {
    protected Renderer renderer;
    
    protected Shape(Renderer renderer) {
        this.renderer = renderer;
    }
    
    public abstract void draw();
}

public class CircleShape extends Shape {
    private int x, y, radius;
    
    public CircleShape(int x, int y, int radius, Renderer renderer) {
        super(renderer);
        this.x = x;
        this.y = y;
        this.radius = radius;
    }
    
    @Override
    public void draw() {
        renderer.renderCircle(x, y, radius);
    }
}

Facade Pattern

Facade pattern provides a simplified interface to a complex subsystem of classes.

public class OrderSystem {
    public void createOrder(String item) {
        System.out.println("Order created for: " + item);
    }
}

public class InventorySystem {
    public boolean checkStock(String item) {
        System.out.println("Checking stock for: " + item);
        return true;
    }
}

public class PaymentSystem {
    public boolean processPayment(double amount) {
        System.out.println("Processing payment: $" + amount);
        return true;
    }
}

public class ShippingSystem {
    public void arrangeShipping(String item, String address) {
        System.out.println("Shipping " + item + " to " + address);
    }
}

public class OrderFacade {
    private OrderSystem orderSystem;
    private InventorySystem inventorySystem;
    private PaymentSystem paymentSystem;
    private ShippingSystem shippingSystem;
    
    public OrderFacade() {
        this.orderSystem = new OrderSystem();
        this.inventorySystem = new InventorySystem();
        this.paymentSystem = new PaymentSystem();
        this.shippingSystem = new ShippingSystem();
    }
    
    public void placeOrder(String item, double price, String address) {
        orderSystem.createOrder(item);
        if (inventorySystem.checkStock(item)) {
            if (paymentSystem.processPayment(price)) {
                shippingSystem.arrangeShipping(item, address);
                System.out.println("Order completed successfully!");
            }
        }
    }
}

Composite Pattern

Composite pattern composes objects into tree structures to represent part-whole hierarchies.

public interface FileSystemComponent {
    void display(String indent);
    int getSize();
}

public class FileItem implements FileSystemComponent {
    private String name;
    private int size;
    
    public FileItem(String name, int size) {
        this.name = name;
        this.size = size;
    }
    
    @Override
    public void display(String indent) {
        System.out.println(indent + "- File: " + name + " (" + size + "KB)");
    }
    
    @Override
    public int getSize() {
        return size;
    }
}

public class Folder implements FileSystemComponent {
    private String name;
    private List<FileSystemComponent> components = new ArrayList<>();
    
    public Folder(String name) {
        this.name = name;
    }
    
    public void add(FileSystemComponent component) {
        components.add(component);
    }
    
    public void remove(FileSystemComponent component) {
        components.remove(component);
    }
    
    @Override
    public void display(String indent) {
        System.out.println(indent + "+ Folder: " + name);
        for (FileSystemComponent component : components) {
            component.display(indent + "  ");
        }
    }
    
    @Override
    public int getSize() {
        int totalSize = 0;
        for (FileSystemComponent component : components) {
            totalSize += component.getSize();
        }
        return totalSize;
    }
}

Flyweight Pattern

Flyweight pattern reduces memory usage by sharing common parts of state between multiple objects.

public interface TreeType {
    void render(int x, int y);
}

public class ConcreteTreeType implements TreeType {
    private String name;
    private String color;
    private String texture;
    
    public ConcreteTreeType(String name, String color, String texture) {
        this.name = name;
        this.color = color;
        this.texture = texture;
    }
    
    @Override
    public void render(int x, int y) {
        System.out.println("Rendering " + name + " tree (" + color + ", " + texture + ") at (" + x + ", " + y + ")");
    }
}

public class TreeFactory {
    private static Map treeTypes = new HashMap<>();
    
    public static TreeType getTreeType(String name, String color, String texture) {
        String key = name + "-" + color + "-" + texture;
        if (!treeTypes.containsKey(key)) {
            treeTypes.put(key, new ConcreteTreeType(name, color, texture));
            System.out.println("Creating new tree type: " + key);
        }
        return treeTypes.get(key);
    }
}

Behavioral Patterns

Template Method Pattern

Template Method defines the skeleton of an algorithm, deferring some steps to subclasses.

public abstract class DataProcessor {
    public final void process() {
        readData();
        processData();
        writeData();
    }
    
    protected abstract void readData();
    protected abstract void processData();
    
    protected void writeData() {
        System.out.println("Writing processed data to output");
    }
}

public class CSVProcessor extends DataProcessor {
    @Override
    protected void readData() {
        System.out.println("Reading data from CSV file");
    }
    
    @Override
    protected void processData() {
        System.out.println("Processing CSV data with comma delimiter");
    }
}

public class JSONProcessor extends DataProcessor {
    @Override
    protected void readData() {
        System.out.println("Reading data from JSON file");
    }
    
    @Override
    protected void processData() {
        System.out.println("Processing JSON data structure");
    }
}

Strategy Pattern

Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable.

public interface PaymentStrategy {
    void pay(double amount);
}

public class CreditCardStrategy implements PaymentStrategy {
    private String cardNumber;
    
    public CreditCardStrategy(String cardNumber) {
        this.cardNumber = cardNumber;
    }
    
    @Override
    public void pay(double amount) {
        System.out.println("Paid $" + amount + " via Credit Card (" + cardNumber + ")");
    }
}

public class CryptoStrategy implements PaymentStrategy {
    private String walletAddress;
    
    public CryptoStrategy(String walletAddress) {
        this.walletAddress = walletAddress;
    }
    
    @Override
    public void pay(double amount) {
        System.out.println("Paid $" + amount + " via Cryptocurrency (" + walletAddress + ")");
    }
}

public class ShoppingCart {
    private PaymentStrategy paymentStrategy;
    
    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }
    
    public void checkout(double amount) {
        paymentStrategy.pay(amount);
    }
}

Command Pattern

Command pattern encapsulates a request as an object, allowing for parameterization and queuing of requests.

public interface Command {
    void execute();
    void undo();
}

public class TextEditor {
    private StringBuilder content = new StringBuilder();
    
    public void write(String text) {
        content.append(text);
        System.out.println("Current content: " + content.toString());
    }
    
    public void erase(int length) {
        int start = content.length() - length;
        if (start >= 0) {
            content.delete(start, content.length());
        }
        System.out.println("Current content: " + content.toString());
    }
}

public class WriteCommand implements Command {
    private TextEditor editor;
    private String text;
    
    public WriteCommand(TextEditor editor, String text) {
        this.editor = editor;
        this.text = text;
    }
    
    @Override
    public void execute() {
        editor.write(text);
    }
    
    @Override
    public void undo() {
        editor.erase(text.length());
    }
}

public class CommandInvoker {
    private Stack<Command> history = new Stack<>();
    
    public void executeCommand(Command command) {
        command.execute();
        history.push(command);
    }
    
    public void undoLastCommand() {
        if (!history.isEmpty()) {
            Command command = history.pop();
            command.undo();
        }
    }
}

Chain of Responsibility Pattern

Chain of Responsibility passes requests along a chain of handlers until one handles it.

public abstract class RequestHandler {
    protected RequestHandler nextHandler;
    
    public RequestHandler setNext(RequestHandler handler) {
        this.nextHandler = handler;
        return handler;
    }
    
    public abstract boolean handle(String request);
}

public class AuthenticationHandler extends RequestHandler {
    @Override
    public boolean handle(String request) {
        if (!request.contains("auth_token")) {
            System.out.println("Authentication failed");
            return false;
        }
        System.out.println("Authentication passed");
        return nextHandler == null || nextHandler.handle(request);
    }
}

public class AuthorizationHandler extends RequestHandler {
    @Override
    public boolean handle(String request) {
        if (!request.contains("admin_role")) {
            System.out.println("Authorization failed");
            return false;
        }
        System.out.println("Authorization passed");
        return nextHandler == null || nextHandler.handle(request);
    }
}

public class ValidationHandler extends RequestHandler {
    @Override
    public boolean handle(String request) {
        if (request.length() < 10) {
            System.out.println("Validation failed");
            return false;
        }
        System.out.println("Validation passed");
        return nextHandler == null || nextHandler.handle(request);
    }
}

State Pattern

State pattern allows an object to alter its behavior when its internal state changes.

public interface VendingState {
    void selectItem();
    void insertMoney(double amount);
    void dispenseItem();
}

public class VendingMachine {
    private VendingState idleState;
    private VendingState hasMoneyState;
    private VendingState soldState;
    private VendingState currentState;
    
    public VendingMachine() {
        idleState = new IdleState(this);
        hasMoneyState = new HasMoneyState(this);
        soldState = new SoldState(this);
        currentState = idleState;
    }
    
    public void setState(VendingState state) {
        this.currentState = state;
    }
    
    public void selectItem() { currentState.selectItem(); }
    public void insertMoney(double amount) { currentState.insertMoney(amount); }
    public void dispenseItem() { currentState.dispenseItem(); }
    
    public VendingState getIdleState() { return idleState; }
    public VendingState getHasMoneyState() { return hasMoneyState; }
    public VendingState getSoldState() { return soldState; }
}

public class IdleState implements VendingState {
    private VendingMachine machine;
    
    public IdleState(VendingMachine machine) {
        this.machine = machine;
    }
    
    @Override
    public void selectItem() {
        System.out.println("Item selected");
        machine.setState(machine.getHasMoneyState());
    }
    
    @Override
    public void insertMoney(double amount) {
        System.out.println("Please select an item first");
    }
    
    @Override
    public void dispenseItem() {
        System.out.println("Please select an item and insert money first");
    }
}

Observer Pattern

Observer pattern defines a one-to-many dependency so that when one object changes state, all dependents are notified.

public interface NewsSubscriber {
    void update(String newsCategory, String headline);
}

public interface NewsPublisher {
    void subscribe(NewsSubscriber subscriber);
    void unsubscribe(NewsSubscriber subscriber);
    void notifySubscribers(String newsCategory, String headline);
}

public class NewsAgency implements NewsPublisher {
    private List<NewsSubscriber> subscribers = new ArrayList<>();
    
    @Override
    public void subscribe(NewsSubscriber subscriber) {
        subscribers.add(subscriber);
    }
    
    @Override
    public void unsubscribe(NewsSubscriber subscriber) {
        subscribers.remove(subscriber);
    }
    
    @Override
    public void notifySubscribers(String newsCategory, String headline) {
        for (NewsSubscriber subscriber : subscribers) {
            subscriber.update(newsCategory, headline);
        }
    }
    
    public void publishNews(String category, String headline) {
        System.out.println("Breaking News in " + category + ": " + headline);
        notifySubscribers(category, headline);
    }
}

public class NewsChannel implements NewsSubscriber {
    private String name;
    
    public NewsChannel(String name) {
        this.name = name;
    }
    
    @Override
    public void update(String newsCategory, String headline) {
        System.out.println(name + " received news [" + newsCategory + "]: " + headline);
    }
}

Mediator Pattern

Mediator pattern defines an object that encapsulates how a set of objects interact.

public interface ChatMediator {
    void sendMessage(String message, User sender);
    void addUser(User user);
}

public abstract class User {
    protected ChatMediator mediator;
    protected String name;
    
    public User(ChatMediator mediator, String name) {
        this.mediator = mediator;
        this.name = name;
    }
    
    public abstract void send(String message);
    public abstract void receive(String message, User sender);
}

public class ChatRoom implements ChatMediator {
    private List<User> users = new ArrayList<>();
    
    @Override
    public void addUser(User user) {
        users.add(user);
    }
    
    @Override
    public void sendMessage(String message, User sender) {
        for (User user : users) {
            if (user != sender) {
                user.receive(message, sender);
            }
        }
    }
}

public class ChatUser extends User {
    public ChatUser(ChatMediator mediator, String name) {
        super(mediator, name);
    }
    
    @Override
    public void send(String message) {
        System.out.println(name + " sends: " + message);
        mediator.sendMessage(message, this);
    }
    
    @Override
    public void receive(String message, User sender) {
        System.out.println(name + " received from " + sender.name + ": " + message);
    }
}

Iterator Pattern

Iterator pattern provides a way to access elements of an aggregate object sequentially without exposing its representation.

public interface BookIterator {
    boolean hasNext();
    Book next();
}

public interface BookCollection {
    BookIterator createIterator();
}

public class Book {
    private String title;
    private String author;
    
    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }
    
    public String getTitle() { return title; }
    public String getAuthor() { return author; }
}

public class Library implements BookCollection {
    private List<Book> books = new ArrayList<>();
    
    public void addBook(Book book) {
        books.add(book);
    }
    
    @Override
    public BookIterator createIterator() {
        return new LibraryIterator(books);
    }
    
    private class LibraryIterator implements BookIterator {
        private List<Book> books;
        private int position = 0;
        
        public LibraryIterator(List<Book> books) {
            this.books = books;
        }
        
        @Override
        public boolean hasNext() {
            return position < books.size();
        }
        
        @Override
        public Book next() {
            return books.get(position++);
        }
    }
}

Visitor Pattern

Visitor pattern separates an algorithm from the object structure it operates on.

public interface ItemVisitor {
    void visit(BookItem book);
    void visit(ElectronicItem electronic);
}

public interface ShoppingCartItem {
    void accept(ItemVisitor visitor);
}

public class BookItem implements ShoppingCartItem {
    private String title;
    private double price;
    
    public BookItem(String title, double price) {
        this.title = title;
        this.price = price;
    }
    
    public String getTitle() { return title; }
    public double getPrice() { return price; }
    
    @Override
    public void accept(ItemVisitor visitor) {
        visitor.visit(this);
    }
}

public class ElectronicItem implements ShoppingCartItem {
    private String name;
    private double price;
    
    public ElectronicItem(String name, double price) {
        this.name = name;
        this.price = price;
    }
    
    public String getName() { return name; }
    public double getPrice() { return price; }
    
    @Override
    public void accept(ItemVisitor visitor) {
        visitor.visit(this);
    }
}

public class PriceCalculatorVisitor implements ItemVisitor {
    private double totalPrice = 0;
    
    @Override
    public void visit(BookItem book) {
        double discountedPrice = book.getPrice() * 0.9;
        totalPrice += discountedPrice;
        System.out.println("Book: " + book.getTitle() + " - $" + discountedPrice);
    }
    
    @Override
    public void visit(ElectronicItem electronic) {
        double taxIncludedPrice = electronic.getPrice() * 1.1;
        totalPrice += taxIncludedPrice;
        System.out.println("Electronic: " + electronic.getName() + " - $" + taxIncludedPrice);
    }
    
    public double getTotalPrice() {
        return totalPrice;
    }
}

Memento Pattern

Memento pattern captures and externalizes an object's internal state without violating encapsulation.

public class EditorState {
    private final String content;
    private final int cursorPosition;
    
    public EditorState(String content, int cursorPosition) {
        this.content = content;
        this.cursorPosition = cursorPosition;
    }
    
    public String getContent() { return content; }
    public int getCursorPosition() { return cursorPosition; }
}

public class TextEditor {
    private String content = "";
    private int cursorPosition = 0;
    
    public void write(String text) {
        content += text;
        cursorPosition = content.length();
    }
    
    public void moveCursor(int position) {
        cursorPosition = Math.min(position, content.length());
    }
    
    public EditorState save() {
        return new EditorState(content, cursorPosition);
    }
    
    public void restore(EditorState state) {
        content = state.getContent();
        cursorPosition = state.getCursorPosition();
    }
    
    public void display() {
        System.out.println("Content: " + content);
        System.out.println("Cursor at: " + cursorPosition);
    }
}

public class EditorHistory {
    private Stack<EditorState> history = new Stack<>();
    
    public void push(EditorState state) {
        history.push(state);
    }
    
    public EditorState pop() {
        if (!history.isEmpty()) {
            return history.pop();
        }
        return null;
    }
}

Interpreter Pattern

Interpreter pattern provides a way to evaluate language grammar or expressions.

public interface MathExpression {
    double interpret();
}

public class NumberExpression implements MathExpression {
    private double number;
    
    public NumberExpression(double number) {
        this.number = number;
    }
    
    @Override
    public double interpret() {
        return number;
    }
}

public class AddExpression implements MathExpression {
    private MathExpression left;
    private MathExpression right;
    
    public AddExpression(MathExpression left, MathExpression right) {
        this.left = left;
        this.right = right;
    }
    
    @Override
    public double interpret() {
        return left.interpret() + right.interpret();
    }
}

public class SubtractExpression implements MathExpression {
    private MathExpression left;
    private MathExpression right;
    
    public SubtractExpression(MathExpression left, MathExpression right) {
        this.left = left;
        this.right = right;
    }
    
    @Override
    public double interpret() {
        return left.interpret() - right.interpret();
    }
}

public class MultiplyExpression implements MathExpression {
    private MathExpression left;
    private MathExpression right;
    
    public MultiplyExpression(MathExpression left, MathExpression right) {
        this.left = left;
        this.right = right;
    }
    
    @Override
    public double interpret() {
        return left.interpret() * right.interpret();
    }
}

Tags: java Design Patterns Software Architecture Object-Oriented Programming Creational Patterns

Posted on Wed, 23 Sep 2026 16:29:57 +0000 by easyedy