Chain of responsibility 디자인 패턴은 행위 디자인 패턴 중 하나입니다.
Chain of Responsibility 디자인 패턴
Chain of responsibility 패턴은 클라이언트로부터의 요청을 처리하기 위해 객체의 체인에 요청이 전달되는 소프트웨어 디자인에서 느슨한 결합을 달성하기 위해 사용됩니다. 그런 다음 체인 안의 객체는 요청을 처리할 객체를 스스로 결정하고 요청을 다음 객체로 전달해야 할지 여부를 결정합니다.
JDK에서의 Chain of Responsibility 패턴 예제
다음은 JDK에서 책임 연쇄 패턴의 예제를 살펴보고 이 패턴의 실제 예제를 구현해보겠습니다. 우리는 try-catch 블록 코드에서 여러 개의 catch 블록을 가질 수 있다는 것을 알고 있습니다. 여기서 각 catch 블록은 특정 예외를 처리하는 처리기 역할을 합니다. 따라서 try 블록에서 예외가 발생하면 해당 예외가 첫 번째 catch 블록으로 전송되어 처리됩니다. catch 블록이 처리할 수 없는 경우 요청을 다음 객체인 채인의 다음 catch 블록으로 전달합니다. 심지어 마지막 catch 블록도 처리할 수 없는 경우 예외는 체인 밖으로 호출 프로그램에게 throw됩니다.
책임 연쇄 디자인 패턴 예제
한 예로 Chain of Responsibility 패턴의 훌륭한 예는 ATM 현금 인출기입니다. 사용자는 출금할 금액을 입력하고 기기는 50달러, 20달러, 10달러 등과 같은 정의된 통화 지폐로 금액을 인출합니다. 사용자가 10의 배수가 아닌 금액을 입력하면 오류가 발생합니다. 이 해결책을 구현하기 위해 Chain of Responsibility 패턴을 사용할 것입니다. 체인은 아래 이미지와 동일한 순서로 요청을 처리할 것입니다. 이 해결책을 단일 프로그램에서 쉽게 구현할 수 있지만, 복잡성이 증가하고 해결책이 더욱 강하게 결합될 것입니다. 따라서 우리는 50달러, 20달러 및 10달러의 지폐를 인출하기 위한 dispense 시스템의 체인을 생성할 것입니다.
Chain of Responsibility 디자인 패턴 – 기본 클래스와 인터페이스
Currency
클래스를 생성할 수 있습니다. 이 클래스는 dispense할 금액을 저장하고 체인 구현에서 사용됩니다. 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;
}
}
기본 인터페이스는 체인에서 다음 프로세서를 정의하는 메서드와 요청을 처리할 메서드를 가져야 합니다. 우리의 ATM Dispense 인터페이스는 아래와 같이 보일 것입니다. DispenseChain.java
package com.journaldev.design.chainofresponsibility;
public interface DispenseChain {
void setNextChain(DispenseChain nextChain);
void dispense(Currency cur);
}
책임 연쇄 패턴 – 연쇄 구현
우리는 DispenseChain
인터페이스를 구현하고 dispense 메서드를 구현할 여러 프로세서 클래스를 만들어야 합니다. 우리 시스템은 50$, 20$, 10$의 세 가지 유형의 통화 지폐와 함께 작동하도록 개발 중이므로 세 가지 구체적인 구현체를 생성합니다. 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);
}
}
}
여기서 주목해야 할 중요한 점은 dispense 메서드의 구현입니다. 각 구현이 요청을 처리하려고 시도하며 금액에 따라 일부 또는 전부를 처리할 수 있습니다. 연쇄 중 하나가 완전히 처리할 수 없으면 해당 요청을 연쇄의 다음 프로세서로 전송하여 나머지 요청을 처리합니다. 프로세서가 아무것도 처리할 수 없으면 동일한 요청을 다음 연쇄로 전달합니다.
책임 연쇄 디자인 패턴 – 연쇄 생성
이것은 매우 중요한 단계이며 체인을 신중하게 생성해야 합니다. 그렇지 않으면 프로세서가 어떠한 요청도 받지 못할 수 있습니다. 예를 들어, 우리의 구현에서 첫 번째 프로세서 체인을 Dollar10Dispenser
로 유지한 다음 Dollar20Dispenser
로 유지한다면, 요청이 두 번째 프로세서로 전달되지 않고 체인이 쓸모없어질 것입니다. 사용자가 요청한 금액을 처리하기 위해 ATM 디스펜서 구현은 다음과 같습니다. ATMDispenseChain.java
package com.journaldev.design.chainofresponsibility;
import java.util.Scanner;
public class ATMDispenseChain {
private DispenseChain c1;
public ATMDispenseChain() {
// 체인 초기화
this.c1 = new Dollar50Dispenser();
DispenseChain c2 = new Dollar20Dispenser();
DispenseChain c3 = new Dollar10Dispenser();
// 책임 체인 설정
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;
}
// 요청 처리
atmDispenser.c1.dispense(new Currency(amount));
}
}
}
위의 응용 프로그램을 실행하면 다음과 같은 출력이 나옵니다.
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.
책임 연쇄 디자인 패턴 클래스 다이어그램
책임 연쇄 디자인 패턴의 ATM 디스펜스 예제는 아래 이미지와 같습니다.
책임 연쇄 디자인 패턴 중요 포인트
- 클라이언트는 어떤 부분이 요청을 처리할지 모르고, 요청을 체인의 첫 번째 객체로 보냅니다. 예를 들어, 프로그램에서 ATMDispenseChain은 요청을 입력된 금액을 인출하는 데 누가 처리하는지를 알지 못합니다.
- 체인의 각 객체는 요청을 처리하는 자체 구현을 가져야 하며, 전체적이거나 부분적이거나 체인의 다음 객체로 보내는 방법이 될 수 있습니다.
- 체인의 각 객체는 체인의 다음 객체에 대한 참조를 가져야 하며, 이는 자바 합성에 의해 달성됩니다.
- 체인을 신중하게 만드는 것이 매우 중요합니다. 그렇지 않으면 특정 프로세서로 요청이 전달되지 않을 수 있거나 체인에 처리할 수 있는 객체가 없을 수 있습니다. 내 구현에서는 사용자가 입력한 금액을 모든 프로세서에서 완전히 처리하도록 확인하는 검사를 추가했지만, 이를 확인하지 않고 요청이 마지막 객체에 도달하고 체인에 더 이상 객체가 없으면 예외를 throw할 수 있습니다. 이것은 설계 결정입니다.
- 책임 연쇄 디자인 패턴은 느슨한 결합을 달성하는 데 좋지만, 많은 구현 클래스와 공통 코드가 대부분인 경우 유지 관리 문제가 발생할 수 있습니다.
JDK에서 Chain of Responsibility 패턴 예시
- java.util.logging.Logger#log()
- javax.servlet.Filter#doFilter()
이것이 Chain of Responsibility 디자인 패턴에 관한 모든 것입니다. 이것이 마음에 들었기를 바라며, 이 디자인 패턴에 대한 이해를 돕기를 희망합니다.
Source:
https://www.digitalocean.com/community/tutorials/chain-of-responsibility-design-pattern-in-java