본문 바로가기
소프트웨어공학/Design Pattern

책임연쇠(chain-of-responsibility pattern)

by 아이티.파머 2023. 1. 5.
반응형

책임연쇠(chain-of-responsibility pattern)

행동패턴중 하나인 책임연쇠패턴이다 근데 어찌보면 우리가 자주사용하는 Spring MVC 의 (혹은 이전 struts ) ServletFilter 처럼 느껴진다. 근데 이건 J2EE 기반기술에 적용된 패턴으로 응답 전,후의 어떤 작업을 할때 사용 할 수 있는것으로(구조패턴중 하나임) 책임연쇠 패턴(행위패턴)과는 다른 의미로 사용된다.

여기서 말한 필터 패턴(Intercepting filter pattern)은, 아키텍처 패던중 하나인 파이프 필터패턴 (pipe-filter pattern)이랑은 다른 내용이다
어째튼 책임연쇠패턴이 뭔지 한번 알아보자.

책임연쇠패턴이란 ?

핸들러의 체인을 따라 요청을 전달할 수 있게 해주는 행동 디자인 패턴이다. 각 핸들러는 요청을 받으면 요청을 처리할지 아니면 체인의 다음핸들러로 전달할지를 결정한다.

이는 요청을 보내는객체와 이를 처리하는 객체간의 결합도를 느슨하게 하기위한 방법이며 여러객체에 처리 기회를 준다.

 

https://refactoring.guru/images/patterns/content/chain-of-responsibility/chain-of-responsibility.png

 

 

책임사슬 연쇠패턴의 다이어그램(위키백과)

 

예시

링크드 리스트를 떠올리면 조금 쉬울것 같다. 순서대로 업무를 처리하게 두는것이다. 즉 업무가 들어왔을때 핸들러에서 내가 처리해야 할것이면 지금 처리하고 끝내고, 그게 아니면 다음핸들러에게 넘긴다. 이걸 반복적으로 수행한다. 인터넷에 많은 예시들이 있는데 은행의 환전관련된 내용이 가장 이해하기 쉬워보인다.

여기서 해결해야할 문제는 다음과 같다.

은행에 들려 ATM 기에 돈을 넣으면 돈을 잔돈으로 환전해주는것이다. 달러를 넣으면 50$, 20$, 10$로 변환해 준다. 만약 10의 배수가 안닌 금액을 입력하면 오류가 발생된다. 이것을 책임 사슬 연쇠 패턴을 사용하여 개발한다.

 

클레스다이어그램 모델

 

 

Currency , 금액을 담고있는 모델

@Setter@Getter
@AllArgsConstructor
public class Currency {
    private  int amount;
}

체인 핸들러 생성

public interface DispenseChain {

    // 다음 체인핸들러를 등록
    void setNextChain(DispenseChain nextChain);
    // 실제 비지니스 로직
    void dispense(Currency currency);
}

핸들러 구현

  • 50$로 교환하는 체인핸들러 구현
public class Dollar50Dispenser implements DispenseChain{

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

    @Override
    public void dispense(Currency currency) {

        if (currency.getAmount() >= 50) {
            int num = currency.getAmount()/50;
            int remainder = currency.getAmount() % 50;
            System.out.println("분배 : " + num + " * 50$ ");
            if (remainder != 0) {
                this.chain.dispense(new Currency(remainder));
            }
        } else {
            this.chain.dispense(currency);
        }

    }
}

Dollar50Dispenser 는 금액이 입력되었을때 50$ 이상이면 50$ 지폐로 교환해주고 나머지 값이 0이 아니거나 50$보다 적으면 다음 체인핸들러를 호출한다. 남은금액이 0이면 그대로 종료 한다.

  • 20$로 교환해주는 체인핸들러 교환
public class Dollar20Dispenser implements DispenseChain{
    private DispenseChain chain;

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

    @Override
    public void dispense(Currency currency) {

        if (currency.getAmount() >= 20) {
            int num = currency.getAmount()/20;
            int remainder = currency.getAmount() % 20;
            System.out.println("분배 : " + num + " * 20$ ");
            if (remainder != 0) {
                this.chain.dispense(new Currency(remainder));
            }
        } else {
            this.chain.dispense(currency);
        }

    }
}

이번엔 20$로 변경해주고 20$보다 적거나 0이 아니면 다음핸들러로 전달 한다. 남은금액이 0이면 그대로 종료

  • 10$로 교환해주는 체인핸들러 생성
public class Dollar10Dispenser implements DispenseChain{
    private DispenseChain chain;

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

    @Override
    public void dispense(Currency currency) {

        if (currency.getAmount() >= 10) {
            int num = currency.getAmount()/10;
            int remainder = currency.getAmount() % 10;
            System.out.println("분배 : " + num + " * 10$ ");
            if (remainder != 0) {
                this.chain.dispense(new Currency(remainder));
            }
        } else {
            this.chain.dispense(currency);
        }

    }
}

10$로 교환해주고 0이 아니면 다음핸들러로 전달한다.

체인핸들러 실행 메인 클레스

public class ChainResponsibilityMain {

    public static void main(String[] args) {
        DispenseChain chain5 = new Dollar50Dispenser();
        DispenseChain chain2 = new Dollar20Dispenser();
        DispenseChain chain1 = new Dollar10Dispenser();

        chain5.setNextChain(chain2);
        chain2.setNextChain(chain1);

        while (true) {
            int amount = 0;
            System.out.println("변환할 금액을 입력해 주세요.");
            Scanner input = new Scanner(System.in);
            amount = Integer.parseInt(input.next());
            if (amount % 10 != 0) {
                System.out.println("10의 배수를 입력해 주세요 ");
                return;
            }
            chain5.dispense(new Currency(amount));
        }

    }
}

결과

변환할 금액을 입력해 주세요.
100
분배 : 2 * 50$ 
변환할 금액을 입력해 주세요.
200
분배 : 4 * 50$ 
변환할 금액을 입력해 주세요.
320
분배 : 6 * 50$ 
분배 : 1 * 20$ 
변환할 금액을 입력해 주세요.
330
분배 : 6 * 50$ 
분배 : 1 * 20$ 
분배 : 1 * 10$ 
변환할 금액을 입력해 주세요.

의견

if else 분기문으로도 충분히 구현할수도 있는 코드이다. 근데 왜 이렇게 디자인패턴을 만들어서 생성해야 할까? 하고 생각해보면 앞서 이야기 했던 부분을 떠올려 보자. 모두 SOLID 원칙(객체지향 5대원칙)을 따르기 위함인것이다. 그렇다고 이게 꼭 객체지향 5대원칙을 따르기 위해서 만들어야 된다기보다는, 기능별로 분리했기에 유지보수를 쉽게 할수있도록 해줌으로써 체인핸들러 패턴을 이용한다고 생각한다. 유지보수중에 이 체인핸들러를 보면 아!! 이건 어떤 의도로 만든 구조구나 하고 생각 할 수있기때문에 좀더 쉽게 이해할 수 있을것이다.

 

응용

또한 이런곳에 응용해볼수도 있을거 같다. 카드나 현금결제를 할때 이곳에 체인을 만들어 두고 각각의 담당을 할 수 있게 하는 것이다. 그림으로 보면 다음과 같다.

각각의 역확을 하는 핸들러를 두고 카드인경우, 체크카드인경우, 현금인경우에 따라 일을 수행하고 아니면 다음 핸들러에게 넘겨서 처리하게 하는것이다.

해당 패턴을 사용하며 주의할점은 체인이 한단계 한단계 진행하다보니 빠른 응답을 필요로하는 곳에서 책임연쇠 패턴을 이용하게 되면 응답이 느릴수도 있다고 한다. 왜냐하면 모든 경우의 수를 한단계씩 진행하며 다음핸들러에게 업무처리를 넘기기 때문에다. 요즘엔 워낙 서버성능도 좋아지고 프로그램도 빨라서 체감하긴 어려울것 같기도하다. 하지만 응답을 바로 요하는 광고나 게임같은경우엔 조금 다를수도 있다고 생각한다.

 

참고

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

반응형