Padrão de Projeto Chain of Responsibility em Java

O padrão de design de cadeia de responsabilidade é um dos padrões de design comportamental.

Padrão de Design de Cadeia de Responsabilidade

O padrão de cadeia de responsabilidade é usado para alcançar o acoplamento solto no design de software, onde uma solicitação do cliente é passada para uma cadeia de objetos para processá-los. Então, o objeto na cadeia decidirá por si mesmo quem processará a solicitação e se a solicitação precisa ser enviada para o próximo objeto na cadeia ou não.

Exemplo de Padrão de Cadeia de Responsabilidade no JDK

Vamos ver o exemplo do padrão de cadeia de responsabilidade no JDK e depois iremos proceder para implementar um exemplo da vida real deste padrão. Sabemos que podemos ter vários blocos catch em um código try-catch. Aqui, cada bloco catch é uma espécie de processador para processar uma exceção específica. Então, quando ocorre qualquer exceção no bloco try, ela é enviada para o primeiro bloco catch para processamento. Se o bloco catch não conseguir processá-la, encaminha a solicitação para o próximo objeto na cadeia, ou seja, para o próximo bloco catch. Se até mesmo o último bloco catch não conseguir processá-la, a exceção é lançada fora da cadeia para o programa chamador.

Exemplo de Padrão de Projeto Cadeia de Responsabilidade

Um dos grandes exemplos do padrão Chain of Responsibility é a máquina de distribuição de caixa eletrônico (ATM). O usuário insere o valor a ser retirado, e a máquina distribui o montante em notas de moeda definidas, como 50$, 20$, 10$, etc. Se o usuário inserir um valor que não seja múltiplo de 10, ocorrerá um erro. Utilizaremos o padrão Chain of Responsibility para implementar essa solução. A cadeia processará a solicitação na mesma ordem que na imagem abaixo. Note que podemos implementar essa solução facilmente em um único programa, mas a complexidade aumentará, e a solução será fortemente acoplada. Portanto, criaremos uma cadeia de sistemas de distribuição para distribuir notas de 50$, 20$ e 10$.

Padrão de Design Chain of Responsibility – Classes Base e Interface

Podemos criar uma classe Moeda que armazenará o valor a ser distribuído e será usada pelas implementações da cadeia. Moeda.java

package com.journaldev.design.chainofresponsibility;

public class Currency {

	private int amount;
	
	public Currency(int amt){
		this.amount=amt;
	}
	
	public int getAmount(){
		return this.amount;
	}
}

A interface base deve ter um método para definir o próximo processador na cadeia e o método que processará a solicitação. Nossa interface de Distribuição de ATM ficará assim. CadeiaDeDistribuicao.java

package com.journaldev.design.chainofresponsibility;

public interface DispenseChain {

	void setNextChain(DispenseChain nextChain);
	
	void dispense(Currency cur);
}

Padrão de Projeto Chain of Responsibilities – Implementações da Cadeia

Precisamos criar diferentes classes de processadores que implementarão a interface DispenseChain e fornecerão a implementação dos métodos de dispensa. Como estamos desenvolvendo nosso sistema para trabalhar com três tipos de notas de moeda – 50$, 20$ e 10$, criaremos três implementações concretas. Dollar50Dispenser.java

package com.journaldev.design.chainofresponsibility;

public class Dollar50Dispenser implements DispenseChain {

	private DispenseChain chain;
	
	@Override
	public void setNextChain(DispenseChain nextChain) {
		this.chain=nextChain;
	}

	@Override
	public void dispense(Currency cur) {
		if(cur.getAmount() >= 50){
			int num = cur.getAmount()/50;
			int remainder = cur.getAmount() % 50;
			System.out.println("Dispensing "+num+" 50$ note");
			if(remainder !=0) this.chain.dispense(new Currency(remainder));
		}else{
			this.chain.dispense(cur);
		}
	}

}

Dollar20Dispenser.java

package com.journaldev.design.chainofresponsibility;

public class Dollar20Dispenser implements DispenseChain{

	private DispenseChain chain;
	
	@Override
	public void setNextChain(DispenseChain nextChain) {
		this.chain=nextChain;
	}

	@Override
	public void dispense(Currency cur) {
		if(cur.getAmount() >= 20){
			int num = cur.getAmount()/20;
			int remainder = cur.getAmount() % 20;
			System.out.println("Dispensing "+num+" 20$ note");
			if(remainder !=0) this.chain.dispense(new Currency(remainder));
		}else{
			this.chain.dispense(cur);
		}
	}

}

Dollar10Dispenser.java

package com.journaldev.design.chainofresponsibility;

public class Dollar10Dispenser implements DispenseChain {

	private DispenseChain chain;
	
	@Override
	public void setNextChain(DispenseChain nextChain) {
		this.chain=nextChain;
	}

	@Override
	public void dispense(Currency cur) {
		if(cur.getAmount() >= 10){
			int num = cur.getAmount()/10;
			int remainder = cur.getAmount() % 10;
			System.out.println("Dispensing "+num+" 10$ note");
			if(remainder !=0) this.chain.dispense(new Currency(remainder));
		}else{
			this.chain.dispense(cur);
		}
	}

}

O ponto importante a ser observado aqui é a implementação do método de dispensa. Você notará que cada implementação está tentando processar a solicitação e, com base no valor, pode processar parte ou toda ela. Se uma das cadeias não conseguir processá-la completamente, ela enviará a solicitação para o próximo processador na cadeia para processar a parte restante da solicitação. Se o processador não conseguir processar nada, ele simplesmente encaminha a mesma solicitação para a próxima cadeia.

Padrão de Projeto Chain of Responsibilities – Criando a Cadeia

Esta é uma etapa muito importante e devemos criar a cadeia cuidadosamente, caso contrário, um processador pode não receber nenhum pedido. Por exemplo, em nossa implementação, se mantivermos a primeira cadeia de processadores como Dollar10Dispenser e depois Dollar20Dispenser, então o pedido nunca será encaminhado para o segundo processador e a cadeia se tornará inútil. Aqui está nossa implementação do Dispensador de Caixas Eletrônicos para processar o valor solicitado pelo usuário. ATMDispenseChain.java

package com.journaldev.design.chainofresponsibility;

import java.util.Scanner;

public class ATMDispenseChain {

	private DispenseChain c1;

	public ATMDispenseChain() {
		// inicializar a cadeia
		this.c1 = new Dollar50Dispenser();
		DispenseChain c2 = new Dollar20Dispenser();
		DispenseChain c3 = new Dollar10Dispenser();

		// definir a cadeia de responsabilidade
		c1.setNextChain(c2);
		c2.setNextChain(c3);
	}

	public static void main(String[] args) {
		ATMDispenseChain atmDispenser = new ATMDispenseChain();
		while (true) {
			int amount = 0;
			System.out.println("Enter amount to dispense");
			Scanner input = new Scanner(System.in);
			amount = input.nextInt();
			if (amount % 10 != 0) {
				System.out.println("Amount should be in multiple of 10s.");
				return;
			}
			// processar o pedido
			atmDispenser.c1.dispense(new Currency(amount));
		}

	}

}

Ao executarmos a aplicação acima, obtemos uma saída como abaixo.

Enter amount to dispense
530
Dispensing 10 50$ note
Dispensing 1 20$ note
Dispensing 1 10$ note
Enter amount to dispense
100
Dispensing 2 50$ note
Enter amount to dispense
120
Dispensing 2 50$ note
Dispensing 1 20$ note
Enter amount to dispense
15
Amount should be in multiple of 10s.

Diagrama de Classe do Padrão de Projeto de Cadeia de Responsabilidades

O exemplo do nosso dispensador de caixas eletrônicos da implementação do padrão de projeto de cadeia de responsabilidades parece com a imagem abaixo.

Pontos Importantes do Padrão de Projeto de Cadeia de Responsabilidades

  • O cliente não sabe qual parte da cadeia irá processar a solicitação e enviará a solicitação para o primeiro objeto na cadeia. Por exemplo, em nosso programa, ATMDispenseChain não tem conhecimento de quem está processando a solicitação para dispensar a quantia inserida.
  • Cada objeto na cadeia terá sua própria implementação para processar a solicitação, seja completa, parcial ou para enviá-la para o próximo objeto na cadeia.
  • Cada objeto na cadeia deve ter referência para o próximo objeto na cadeia a fim de encaminhar a solicitação, isso é alcançado por meio de composição em Java.
  • É crucial criar a cadeia cuidadosamente; caso contrário, pode haver uma situação em que a solicitação nunca será encaminhada para um processador específico, ou não haverá objetos na cadeia capazes de lidar com a solicitação. Em minha implementação, adicionei a verificação para a quantia inserida pelo usuário para garantir que seja processada completamente por todos os processadores, mas podemos optar por não verificar e lançar uma exceção se a solicitação chegar ao último objeto e não houver mais objetos na cadeia para encaminhar a solicitação. Isso é uma decisão de design.
  • O padrão de design Chain of Responsibility é bom para alcançar um acoplamento flexível, mas isso vem com a compensação de ter muitas classes de implementação e problemas de manutenção se a maior parte do código for comum em todas as implementações.

Exemplos de Padrão de Cadeia de Responsabilidade no JDK

  • java.util.logging.Logger#log()
  • javax.servlet.Filter#doFilter()

Isso é tudo para o padrão de design Chain of Responsibility. Espero que você tenha gostado e que isso tenha ajudado a esclarecer sua compreensão sobre esse padrão de design.

Source:
https://www.digitalocean.com/community/tutorials/chain-of-responsibility-design-pattern-in-java