Padrão de Design Chain of Responsibility em Java

Corrente de responsabilidade é um dos padrões de projeto comportamentais.

Padrão de Projeto Corrente de Responsabilidade

O padrão de corrente de responsabilidade é utilizado para alcançar um acoplamento flexível no design de software, onde uma solicitação do cliente é passada para uma cadeia de objetos para processá-los. Em seguida, o objeto na cadeia decidirá por si só quem irá 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 Corrente de Responsabilidade em JDK

Vamos ver o exemplo do padrão de cadeia de responsabilidade no JDK e depois vamos prosseguir para implementar um exemplo da vida real desse padrão. Sabemos que podemos ter vários blocos catch em um código de bloco try-catch. Aqui, cada bloco catch é uma espécie de processador para lidar com aquela 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, o próximo bloco catch. Se mesmo o último bloco catch não conseguir processar, a exceção é lançada fora da cadeia para o programa chamador.

Exemplo de Padrão de Design de Cadeia de Responsabilidade

Um dos ótimos exemplos do padrão Chain of Responsibility é a máquina de saque automático (ATM). O usuário insere o valor a ser sacado, e a máquina libera a quantia 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. Observe que podemos implementar essa solução facilmente em um único programa, mas a complexidade aumentará, e a solução ficará fortemente acoplada. Portanto, criaremos uma cadeia de sistemas de saque para liberar notas de 50$, 20$ e 10$.

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

Podemos criar uma classe Currency que armazenará o valor a ser liberado e será usada pelas implementações da cadeia. Currency.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 ATM Dispense ficará assim. DispenseChain.java

package com.journaldev.design.chainofresponsibility;

public interface DispenseChain {

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

Padrão de Design Chain of Responsibilities – Implementações de 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 cédulas – 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 aqui é observar 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 todo o valor. Se uma das cadeias não conseguir processar completamente, ela envia a solicitação para o próximo processador na cadeia para processar a parte restante. Se o processador não conseguir processar nada, ele simplesmente encaminha a mesma solicitação para a próxima cadeia.

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

Este é um passo muito importante e devemos criar a cadeia com cuidado, caso contrário, um processador pode não estar recebendo nenhuma solicitação. Por exemplo, em nossa implementação, se mantivermos a primeira cadeia de processadores como Dollar10Dispenser e depois Dollar20Dispenser, então a solicitação nunca será encaminhada para o segundo processador e a cadeia se tornará inútil. Aqui está nossa implementação do Dispensador de ATM 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 a solicitação
			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 Chain of Responsibilities

O exemplo de dispensador de ATM de nossa implementação do padrão de projeto chain of responsibility se parece com a imagem abaixo.

Pontos Importantes do Padrão de Projeto Chain of Responsibility

  • Cliente não sabe qual parte da cadeia 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, e isso é alcançado por meio de composição em Java.
  • Criar a cadeia cuidadosamente é muito importante; caso contrário, pode acontecer de a solicitação nunca ser encaminhada para um processador específico ou de 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 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 baixo acoplamento, mas vem com o compromisso 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 tenha conseguido esclarecer seu entendimento sobre este padrão de design.

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