Chain of Responsibility

Separation of Concerns advises separating an application into multiple small units, each unit will have its own designation functionality and will not overlap with other units.
A request requires multiple processing. a single unit can have all the functions but as the request and functionality grow, it will become hard to maintain everything in a single unit.
the unit will become very complex and changing the functionality will become very error-prone. it will make the code tightly coupled.
 
A Chain of Responsibility pattern helps to achieve Loose Coupling. It divides the functionality into multiple subunits and the request will pass through the chain of individual small functions to process them. each unit will decide how to process the request and pass it forward if further processing is required.

Chain of Responsibility can be easily visualized via the example of an ATM machine.

suppose the machine dispenses 3 types of currency bills, $1000, $100, and $10.

a user enters the $2220 amount,

  • the $1000 processing unit will process the $2000 amount and pass the remaining amount to the next chain.
  • The $100 processing unit will process the $200 and pass the remaining amount to the next chain.
  • The $10 processing unit will process the remaining $20, and the total amount is processed now.

another great example of Chain of Responsibility is determining the state of an Apple Fruit. Apple passes through multiple stages in its lifetime such as SEED, PLANT, TREE and FRUIT before coming out for the market.
each chain of units will process the apple and try to determine the current state else will pass to the next chain of processors.

each sub-unit processor in the chain has the capacity to decide whether to fully, or partially process the request or pass it to the next unit and each chain has a reference of the next unit via composition.

package org.wesome.design.patterns;

public enum Stage {
    SEED, PLANT, TREE, FRUIT, ROTTEN
}
package org.wesome.design.patterns;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public class Apple {
    private String name;
    private Stage stage;
}
package org.wesome.design.patterns;

import lombok.AllArgsConstructor;

@AllArgsConstructor
public abstract class AppleProcessor {
    private final AppleProcessor appleProcessor;

    public Stage process(Apple apple) {
        if (appleProcessor != null) {
            return appleProcessor.process(apple);
        }
        System.out.println("invalid stage");
        return null;
    }
}
package org.wesome.design.patterns;

import lombok.NoArgsConstructor;

@NoArgsConstructor
public class ChainProcessor {
    AppleProcessor appleProcessor;

    public void buildChain() {
        appleProcessor = new SeedProcessor(new PlantProcessor(new TreeProcessor(new FruitProcessor(null))));
    }

    public Stage process(Apple request) {
        return appleProcessor.process(request);
    }
}
package org.wesome.design.patterns;

public class FruitProcessor extends AppleProcessor {
    public FruitProcessor(AppleProcessor appleProcessor) {
        super(appleProcessor);
    }

    public Stage process(Apple request) {
        if (Stage.FRUIT == request.getStage()) {
            System.out.println(request.getName() + " is at fruit stage and ready for the market");
            return Stage.FRUIT;
        } else {
            System.out.println(request.getName() + " has passed fruit stage, checking next stage");
            return super.process(request);
        }
    }
}
package org.wesome.design.patterns;

public class PlantProcessor extends AppleProcessor {
    public PlantProcessor(AppleProcessor appleProcessor) {
        super(appleProcessor);
    }

    public Stage process(Apple request) {
        if (Stage.PLANT == request.getStage()) {
            System.out.println(request.getName() + " is at plant stage and requires to be watered");
            return Stage.PLANT;
        } else {
            System.out.println(request.getName() + " has passed plant stage, checking next stage");
            return super.process(request);
        }
    }
}
package org.wesome.design.patterns;

public class SeedProcessor extends AppleProcessor {
    public SeedProcessor(AppleProcessor appleProcessor) {
        super(appleProcessor);
    }

    public Stage process(Apple request) {
        if (Stage.SEED == request.getStage()) {
            System.out.println(request.getName() + " is at seed stage and ready for planting");
            return Stage.SEED;
        } else {
            System.out.println(request.getName() + " has passed seed stage, checking next stage");
            return super.process(request);
        }
    }
}
package org.wesome.design.patterns;

public class TreeProcessor extends AppleProcessor {
    public TreeProcessor(AppleProcessor appleProcessor) {
        super(appleProcessor);
    }

    public Stage process(Apple request) {
        if (Stage.TREE == request.getStage()) {
            System.out.println(request.getName() + " is at tree stage and ready for harvesting");
            return Stage.TREE;
        } else {
            System.out.println(request.getName() + " has passed tree stage, checking next stage");
            return super.process(request);
        }
    }
}
package org.wesome.design.patterns;

public class ChainOfResponsibility {
    public static void main(String[] args) {
        ChainProcessor chainProcessor = new ChainProcessor();
        chainProcessor.buildChain();
        //Calling chain of responsibility
        chainProcessor.process(new Apple("Macintosh", Stage.FRUIT));
        chainProcessor.process(new Apple("Fuji", Stage.TREE));
        chainProcessor.process(new Apple("Gala", Stage.PLANT));
        chainProcessor.process(new Apple("Jonagold", Stage.SEED));
    }
}
package org.wesome.design.patterns;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class AppleChainTest {
    @Test
    void testFruitStage() {
        ChainProcessor chainProcessor = new ChainProcessor();
        chainProcessor.buildChain();
        Assertions.assertEquals(Stage.FRUIT, chainProcessor.process(new Apple("Macintosh", Stage.FRUIT)));
    }

    @Test
    void testTreeStage() {
        ChainProcessor chainProcessor = new ChainProcessor();
        chainProcessor.buildChain();
        Assertions.assertEquals(Stage.TREE, chainProcessor.process(new Apple("Fuji", Stage.TREE)));
    }

    @Test
    void testPlantStage() {
        ChainProcessor chainProcessor = new ChainProcessor();
        chainProcessor.buildChain();
        Assertions.assertEquals(Stage.PLANT, chainProcessor.process(new Apple("Gala", Stage.PLANT)));
    }

    @Test
    void testSeedStage() {
        ChainProcessor chainProcessor = new ChainProcessor();
        chainProcessor.buildChain();
        Assertions.assertEquals(Stage.SEED, chainProcessor.process(new Apple("Jonagold", Stage.SEED)));
    }

    @Test
    void testInvalidStage() {
        ChainProcessor chainProcessor = new ChainProcessor();
        chainProcessor.buildChain();
        Assertions.assertNull(chainProcessor.process(new Apple("Jonagold", Stage.ROTTEN)));
    }
}
plugins {
    id 'java'
    id "io.freefair.lombok" version "6.2.0"
}

group = 'org.wesome'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = JavaVersion.VERSION_1_8

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.6.2'
}

test {
    useJUnitPlatform()
}

follow us on