Behavioral Design Patterns: Chain of Responsibility and Command in Java

Chain of Responsibility Pattern

The Chain of Responsibility pattern decouples request senders from receivers by passing requests along a chain of handlers. Each handler either processes the request or forwards it to the next handler in the chain. This pattern is useful for scenarios like approval workflows, logging systems, or HTTP interceptors.

Structure

  • Handler: Declares an interface for handling requests and optionally defines a method to set the next handler.
  • ConcreteHandler: Implements the handling logic and decides whether to process the request or delegate it.
  • Client: Initiates the request to the first handler in the chain.

Example Implementation

Consider an approval system with three levels: Supervisor, DepartmentManager, and GeneralManager. Each can approve requests based on a priority level.

abstract class Approver {
    protected Approver nextApprover;

    public void setNext(Approver approver) {
        this.nextApprover = approver;
    }

    public abstract void processRequest(int priority);
}

class Supervisor extends Approver {
    @Override
    public void processRequest(int priority) {
        if (priority <= 1) {
            System.out.println("Supervisor approved the request.");
        } else if (nextApprover != null) {
            System.out.println("Supervisor escalated the request.");
            nextApprover.processRequest(priority);
        }
    }
}

class DepartmentManager extends Approver {
    @Override
    public void processRequest(int priority) {
        if (priority <= 2) {
            System.out.println("Department Manager approved the request.");
        } else if (nextApprover != null) {
            System.out.println("Department Manager escalated the request.");
            nextApprover.processRequest(priority);
        }
    }
}

class GeneralManager extends Approver {
    @Override
    public void processRequest(int priority) {
        System.out.println("General Manager handled the request (approved or denied)."));
    }
}

Usage

public class ChainDemo {
    public static void main(String[] args) {
        Approver supervisor = new Supervisor();
        Approver deptMgr = new DepartmentManager();
        Approver genMgr = new GeneralManager();

        supervisor.setNext(deptMgr);
        deptMgr.setNext(genMgr);

        supervisor.processRequest(1); // Approved by Supervisor
        supervisor.processRequest(3); // Escalated to General Manager
    }
}

Pros and Cons

Advantages:

  • Reduces coupling between sender and receivers.
  • Enhances flexibility in assigning responsibilities.
  • Easy to add new handlers.

Disadvantages:

  • No guarantee that a request will be handled.
  • Long chains may impact performance and readability.

Command Pattern

The Command pattern encapsulates a request as an object, there by allowing parameterization of clients with queues, requests, and operations. It supports undoable operations and logging.

Structure

  • Command: Declares an interface for executing an operation.
  • ConcreteCommand: Binds a receiver to an action and implements execute() by invoking the corresponding method on the receiver.
  • Invoker: Holds and triggers command objects.
  • Receiver: Knows how to perform the actual work.

Example Implementation

A student receives commands from teachers—e.g., clean classroom or do homework—but can only execute one due to time constraints.

class Student {
    public void cleanRoom(String name) {
        System.out.println(name + " is cleaning the classroom.");
    }

    public void finishHomework(String name) {
        System.out.println(name + " is finishing homework.");
    }
}

interface TaskCommand {
    void execute(String studentName);
}

class CleanRoomCommand implements TaskCommand {
    private Student student;

    public CleanRoomCommand(Student student) {
        this.student = student;
    }

    @Override
    public void execute(String name) {
        student.cleanRoom(name);
    }
}

class HomeworkCommand implements TaskCommand {
    private Student student;

    public HomeworkCommand(Student student) {
        this.student = student;
    }

    @Override
    public void execute(String name) {
        student.finishHomework(name);
    }
}

class TeacherInvoker {
    private TaskCommand primaryTask;

    public void assignTask(TaskCommand command) {
        if (primaryTask == null) {
            primaryTask = command;
        } else {
            System.out.println("Ignoring additional task due to time limit.");
        }
    }

    public void executeAssignedTask(String name) {
        if (primaryTask != null) {
            primaryTask.execute(name);
        }
    }
}

Usage

public class CommandDemo {
    public static void main(String[] args) {
        Student student = new Student();
        TeacherInvoker invoker = new TeacherInvoker();

        invoker.assignTask(new CleanRoomCommand(student));
        invoker.assignTask(new HomeworkCommand(student)); // Ignored

        invoker.executeAssignedTask("Alex");
    }
}

Pros and Cons

Advantages:

  • Decouples invoker from receiver.
  • Supports queuing, logging, and undo operations.
  • Easy to extend with new commmands.

Disadvantages:

  • Can lead to many small classes.
  • Overhead when commands are simple.

Tags: java Design Patterns Chain of Responsibility Command Pattern Behavioral Patterns

Posted on Mon, 18 May 2026 13:39:38 +0000 by CookieDoh