In today’s design pattern section, we’ll investigate the Chain of responsibility behavioral pattern.

Navigating the complex maze of corporate hierarchies for a salary adjustment turns into a strategic journey—each step, from Team Lead to HR Manager, acts as a gatekeeper in this navigable yet challenging process.

This journey, often mired in bureaucracy, requires approvals from multiple managerial levels, each with its own set of criteria and considerations. Enter the Chain of responsibility design pattern.

Police officer humorously breaking down door - Chain of Responsibility Implementation.

By mirroring the decision-making process found in organizations, this pattern offers a structured approach to solving the age-old dilemma of securing a well-deserved raise.

Understanding the Chain of Responsibility Pattern

At its core, the chain of responsibility pattern is a masterpiece of design simplicity and elegance. It allows an object to send a command without knowing which system component will handle the request.

Imagine a chain of command in a military operation or a series of managerial approvals in our case—each link in the chain has the potential to act, pass the request along, or both. This pattern not only encapsulates command and control in a neat package but also reflects the real-world intricacies of workplace dynamics and salary negotiations.

Setting the Stage with a Real-World Scenario

Consider John, a dedicated software engineer eyeing a promotion and a raise.

John’s request must ascend the corporate ladder, seeking nods from his Team Lead, Line Manager, and finally, the HR Manager—each wielding the power to propel or quash his aspirations.

Illustrated hierarchy for salary adjustment approval: Team Lead, Line Manager, HR Manager connected by a chain.

This scenario sets the perfect stage for demonstrating the chain of responsibility pattern in action.

Building the app 🏗️

Crafting the Salary Adjustment Request Class

The heart of our app beats within the SalaryAdjustmentRequest class, a fundamental component of our Chain of Responsibility implementation.

Here, we encapsulate the essence of an employee’s name, current salary, and desired raise.

These fields, marked private for encapsulation, form the payload that traverses our chain of responsibility, seeking approvals at every juncture.


package org.compensation.model;

public class SalaryAdjustmentRequest {
    private String employeeName; // example: John doe
    private int currentSalary; // example: 3200 (dollars)
    private int requestRaise; // example: 300 (dollars)

    public SalaryAdjustmentRequest(String employeeName, int currentSalary, int requestRaise) {
        this.employeeName = employeeName;
        this.currentSalary = currentSalary;
        this.requestRaise = requestRaise;
    }

    public String getEmployeeName() {
        return employeeName;
    }

    public void setEmployeeName(String employeeName) {
        this.employeeName = employeeName;
    }

    public int getCurrentSalary() {
        return currentSalary;
    }

    public void setCurrentSalary(int currentSalary) {
        this.currentSalary = currentSalary;
    }

    public int getRequestRaise() {
        return requestRaise;
    }

    public void setRequestRaise(int requestRaise) {
        this.requestRaise = requestRaise;
    }
}

Implementing the Chain with Handler Classes

The magic unfolds with the creation of handler classes: TeamLeadHandler, LineManagerHandler, and HRManagerHandler.

Each class, adhering to the CompensationHandler interface, plays a pivotal role in the approval process.


package org.compensation.handler;

import org.compensation.model.SalaryAdjustmentRequest;

public interface CompensationHandler {
    void setNextHandler(CompensationHandler nextHandler);
    void handleRequest(SalaryAdjustmentRequest request);
}

They scrutinize the request, weighing it against their criteria, and decide its fate—to approve, reject, or pass it up the chain.


package org.compensation.handler;

import org.compensation.model.SalaryAdjustmentRequest;

import java.util.Scanner;

public class TeamLeadHandler implements CompensationHandler {
    private CompensationHandler nextHandler;

    @Override
    public void setNextHandler(CompensationHandler nextHandler) {
        this.nextHandler = nextHandler;
    }

    @Override
    public void handleRequest(SalaryAdjustmentRequest request) {
        Scanner scanner = new Scanner(System.in);
        System.out.println(
            "Do you want to approve Salary Adjustment for " + request.getEmployeeName() + " ?"
        );

        String input = scanner.nextLine();

        if ("Y".equalsIgnoreCase(input)) {
            System.out.println(
                "TeamLead: I approve this request for " + request.getEmployeeName()
            );

            nextHandler.handleRequest(request); // pass it to the Line manager...
        } else {
            // TeamLead decided not to approve the salary adjustment, so we'll just print a message
            System.out.println("TeamLead: Sorry, we don't have a budget...");
        }
    }
}

package org.compensation.handler;

import org.compensation.model.SalaryAdjustmentRequest;

import java.util.Scanner;

public class LineManagerHandler implements CompensationHandler {
    private CompensationHandler nextHandler;

    @Override
    public void setNextHandler(CompensationHandler nextHandler) {
        this.nextHandler = nextHandler;
    }

    @Override
    public void handleRequest(SalaryAdjustmentRequest request) {
        Scanner scanner = new Scanner(System.in);
        System.out.println(
            "Do you want to approve Salary Adjustment for " + request.getEmployeeName() + " ?"
        );

        String input = scanner.nextLine();

        if ("Y".equalsIgnoreCase(input)) {
            System.out.println(
                "LineManager: I approve this request for " + request.getEmployeeName()
            );

            nextHandler.handleRequest(request); // pass it to the HR manager...
        } else {
            // LineManager decided not to approve the salary adjustment, so we'll just print a message
            System.out.println("LineManager: Sorry, we don't have a budget...");
        }
    }
}

package org.compensation.handler;

import org.compensation.model.SalaryAdjustmentRequest;

import java.util.Scanner;

public class HRManager implements CompensationHandler {
    private CompensationHandler nextHandler;

    @Override
    public void setNextHandler(CompensationHandler nextHandler) {
        this.nextHandler = nextHandler;
    }

    @Override
    public void handleRequest(SalaryAdjustmentRequest request) {
        Scanner scanner = new Scanner(System.in);
        System.out.println(
            "Do you want to approve Salary Adjustment for " + request.getEmployeeName() + " ?"
        );

        String input = scanner.nextLine();

        if ("Y".equalsIgnoreCase(input)) {
            System.out.println(
                "HRManager: I approve this request for " + request.getEmployeeName()
            );

            // this is a final stop...
        } else {
            // HRManager decided not to approve the salary adjustment, so we'll just print a message
            System.out.println("HRManager: Sorry, we don't have a budget...");
        }
    }
}

Demonstrating the Process in Action

To test the effectiveness of CompensationChain, we simulate two scenarios that highlight the Chain of Responsibility implementation’s flexibility and its capability to handle real-life complexities with grace.


package org.compensation;

import org.compensation.handler.CompensationHandler;
import org.compensation.handler.HRManager;
import org.compensation.handler.LineManagerHandler;
import org.compensation.handler.TeamLeadHandler;
import org.compensation.model.SalaryAdjustmentRequest;

public class Main {
    public static void main(String[] args) {
//        System.out.println("Hello world!");

        CompensationHandler teamLead = new TeamLeadHandler();
        CompensationHandler lineManager = new LineManagerHandler();
        CompensationHandler hrManager = new HRManager();

        // set the chain of responsibility
        teamLead.setNextHandler(lineManager);
        lineManager.setNextHandler(hrManager);

        // sample salary adjustment request
        SalaryAdjustmentRequest request = new SalaryAdjustmentRequest(
            "John Doe",
            3200,
            300
        );

        // Start the process at the first handler (team lead)
        teamLead.handleRequest(request);

    }
}

These tests not only highlight the pattern’s flexibility but also its capability to handle real-life complexities with grace.

Screenshot of app testing Chain of Responsibility pattern with salary adjustment approval flow for John Doe.

Note: You can find the complete project on my GitHub page.

A Roadblock at HR: Understanding Rejection 🚧

In the world of ‘CompensationChain’, not every journey ends in celebration.

When John’s request reaches the HR Manager, it encounters a final, decisive review. This stage is crucial, as it represents the organization’s broader interests, balancing individual aspirations against policy and budgetary constraints.

A rejection here, while disheartening, is not just an end but a moment of reflection and learning, demonstrating the pattern’s ability to gracefully handle denial and provide feedback.

App screenshot showing Chain of Responsibility pattern handling a salary adjustment request rejection for John Doe.

The Power of Chain of Responsibility Design 🔋

CompensationChain is more than just a Java application; it’s a testament to the power of strategic software design in addressing real-world challenges.

By simulating the salary adjustment request process, we uncover the potential of the Chain-of-Responsibility pattern to bring order and clarity to complex decision-making workflows.

It teaches us that with the right design patterns, software can not only solve technical problems but also mirror and manage the intricacies of human organizational structures.

Speaking of patterns, ever wondered what do Max Verstappen and Strategy design pattern have in common? Check out Strategy Pattern in Java to find out! 🏎️

Happy coding! ⌨️

Categorized in:

Design Patterns, Java,

Last Update: February 15, 2024