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