Sunday, June 12, 2022

Design Pattern: Chain-of-responsibility pattern

Chapters

Chain-of-responsibility pattern

Chain-of-responsibility pattern is a behavioral design pattern that consists of command objects and processing objects. Command objects are objects that are being processed by processing objects.

Typically, every class in the chain has different responsibilities from one another. However, many implementations(such as UI event handling, servlet filters in Java and the example below) breaks this concept and allow several classes in the chain to take the same responsibility. This pattern promotes loose coupling as its processing objects are not closely tied up to command objects.

This example demonstrates chain-of-responsibility pattern.
import java.util.List;
import java.util.Arrays;

public class ClientCode{
  
  public static void main(String[] args){
  
    Handler handler = 
    new Adult(Arrays.asList(Handler.Fruits.all()), "Timothy").
    addHandler(
     new YoungAdult(
     Arrays.asList(Handler.Fruits.APPLE, Handler.Fruits.GUAVA),
                   "Samantha")).
    addHandler(
     new Child(
     Arrays.asList(Handler.Fruits.APPLE, Handler.Fruits.ORANGE),
                   "Louis"));
                   
     handler.offer(Handler.Fruits.APPLE);
     System.out.println();
     handler.offer(Handler.Fruits.GUAVA);
     System.out.println();
     handler.offer(Handler.Fruits.ORANGE);
     System.out.println();
     handler.offer(Handler.Fruits.MELON);
  }
}

//functional interface
interface Handler{
  public enum Fruits{
    AVOCADO, ORANGE, APPLE, GUAVA, MELON;
    
    public static Fruits[] all(){
      return values();
    }
  }
  
  //No need to add Handler reference after
  //Fruits reference. This method is in the
  //scope of Handler already
  //
  //classes that implement this method also
  //don't need to add Handler reference after
  //Fruits reference
  void offer(Fruits fruit);
  
  default Handler addHandler(Handler nextHandler){
    return (fruit) -> {
      offer(fruit);
      nextHandler.offer(fruit);
    };
  }
  
}

abstract class Patron{
  protected List<Handler.Fruits> preferredFruit;
  protected String name;
  
  Patron(List<Handler.Fruits> preferredFruit, 
         String name){
    this.preferredFruit = preferredFruit;
    this.name = name;
  }
  
  protected boolean checkPreferredFruit(Handler.Fruits fruit){
    boolean result = false;
    
    for(Handler.Fruits f : preferredFruit)
      if(f == fruit)
        result = true;
    return result;
  }
  
}

class Child extends Patron implements Handler{
  
  Child(List<Handler.Fruits> preferredFruit, 
         String name){
    super(preferredFruit, name);
  }
  
  @Override
  public void offer(Fruits fruit){
    if(!checkPreferredFruit(fruit))
      return;
    
    System.out.println
    (name + ", a child, took " + fruit);
  }
}

class YoungAdult extends Patron implements Handler{
  
  YoungAdult(List<Handler.Fruits> preferredFruit, 
         String name){
    super(preferredFruit, name);
  }
  
  @Override
  public void offer(Fruits fruit){
    if(!checkPreferredFruit(fruit))
      return;
  
    System.out.println
    (name + ", a young adult, took " + fruit);
  }
}

class Adult extends Patron implements Handler{
  
  Adult(List<Handler.Fruits> preferredFruit, 
         String name){
    super(preferredFruit, name);
  }
  
  @Override
  public void offer(Fruits fruit){
    if(!checkPreferredFruit(fruit))
      return;
    
    System.out.println
    (name + ", an adult, took " + fruit);
  }
}

Result
Timothy, an adult, took APPLE
Samantha, a young adult, took APPLE
Louis, a child, took APPLE

Timothy, an adult, took GUAVA
Samantha, a young adult, took GUAVA

Timothy, an adult, took ORANGE
Louis, a child, took ORANGE

Timothy, an adult, took MELON
In the example above, fruits in the Fruits enum are command objects whereas Adult, Child and YoungAdult instances are processing objects.

No comments:

Post a Comment