본문 바로가기
Programming

디자인패턴 시리즈 12. 책임 연쇄 패턴 (chain-of-responsibility Pattern)

by LeeJ1Hyun 2023. 1. 25.

책임 연쇄 패턴 (chain-of-responsibility Pattern)

1개의 요청을 2개 이상의 객체에서 처리해야 할 때 사용하면 좋은 패턴이다.

코드에 적용해보기

요청을 처리하는 인터페이스를 정의하고 여러 개의 객체를 체인 형태로 연결하여 책임을 맡기는 형태이다. 각각의 객체들은 해당 요청을 처리하지 못하는 경우에 다음 체인으로 책임을 넘기게 된다. 예를 들면 고객의 소리함이라고 하는 서비스를 운영한다고 하자. 소리를 보내는 방법은 이메일을 보내는 것이다. 그런데 이 메일들을 살펴보면 크게 3가지 특징이 있다. 각각 서비스에 대한 불만을 얘기하는 메일, 특정 상품을 출시 해달라는 메일, 스팸 메일이다. 순서대로 CS팀, MD에게 메일을 처리하게 하고 스팸 메일은 삭제 처리해야 한다.

 

 

메일을 종류에 따라 필터링 해주는 프로그램을 책임 연쇄 패턴을 통해 구현할 수 있다.

 

public abstract class Sortor {
    Sortor nextNode;
    protected String mail;
    protected String name;

    public Sortor setNext(Sortor next) {
        Sortor sortor = this;
        while (sortor.nextNode != null)
            sortor = sortor.nextNode;
        sortor.nextNode = next;
        return this;
    }

    public boolean handlerRequest(String requestMail, String request) {
        if (mail == requestMail) {
            processRequest(request);
            return true;
        } else {
            System.out.println(requestMail + " 은 " + name + "에서 처리할 수 없습니다. 다음 분류 객체에게 맡기세요.");
            return nextNode != null ? nextNode.handlerRequest(requestMail, request) : false;
        }
    }

    abstract void processRequest(String request);
}

 

메일을 분류하는 Sortor 인터페이스를 만들었다. 이를 이용하여 상속 받는 서브 클래스들에게 책임을 위임한다. setNext() 메소드는 링크드 리스트처럼 서브 클래스간의 이동을 담당한다. handlerRequest() 메소드는 현재 객체가 해당 일을 처리하는 담당이 아니라면 다음 차례의 객체에게 처리를 요청하여 위임한다. 

 

public class DiscontentMail extends Sortor {
    public DiscontentMail(String mail) {
        this.mail = mail;
        this.name = "DiscontentMail";
    }

    @Override
    void processRequest(String request) {
        System.out.println("execute " + this.name + " : " + request);
        System.out.println("서비스 불만 메일이 CS팀에게 전달되었습니다.");
    }
}

 

public class NewReleaseMail extends Sortor {
    public NewReleaseMail(String mail) {
        this.mail = mail;
        this.name = "NewReleaseMail";
    }

    @Override
    void processRequest(String request) {
        System.out.println("execute " + this.name + " : " + request);
        System.out.println("제품 출시 요청 메일이 MD에게 전달되었습니다.");
    }
}

 

public class SpamMail extends Sortor {
    public SpamMail(String mail) {
        this.mail = mail;
        this.name = "SpamMail";
    }

    @Override
    void processRequest(String request) {
        System.out.println("execute " + this.name + " : " + request);
        System.out.println("스팸 메일이 삭제 되었습니다.");
    }
}

 

각각의 분류기들은 processRequest() 메소드를 상속 받아 역할에 맞는 처리를 구현한다.

 

public class Client {
    public static void main(String[] args) {

        Sortor sortor = new DiscontentMail("DiscontentMail")
                .setNext(new NewReleaseMail("NewReleaseMail"))
                .setNext(new SpamMail("SpamMail"));

        System.out.println("요청 : 스팸메일 분류");
        if(sortor.handlerRequest("SpamMail", "스팸메일 분류")) {
            System.out.println("분류가 완료되었습니다!");
        } else {
            System.out.println("잘못된 요청입니다!");
        }
    }
}

// 요청 : 스팸메일 분류
// SpamMail 은 DiscontentMail에서 처리할 수 없습니다. 다음 분류 객체에게 맡기세요.
// SpamMail 은 NewReleaseMail에서 처리할 수 없습니다. 다음 분류 객체에게 맡기세요.
// execute SpamMail : 스팸메일 분류
// 스팸 메일이 삭제 되었습니다.
// 분류가 완료되었습니다!

 

클라이언트에서는 Sortor 객체를 생성할 때 분류기 객체들을 생성하고 setNext() 메소드를 통해 Node를 연결해준다. handlerRequest() 메소드를 통해 스팸 메일을 처리하도록 하면 여러 분류 클래스를 건너뛰고 올바른 SpamMail 클래스가 이를 처리하는 것을 알 수 있다.

 

모든 이메일들은 가장 첫 번째 핸들러에게 전달되고, 차례대로 넘어가게 된다. 어느 누구도 처리하지 못해 사슬 맨 끝까지 간 이메일은 처리되지 못하므로 필요에 따라 모든 요청을 포괄적으로 처리하는 핸들러를 만들 수도 있다.

 

요청을 보내는 쪽과 받는 쪽을 분류할 수 있고, 객체는 사슬의 구조를 몰라도 상관이 없다. 또한 사슬에 들어있는 다른 객체의 직접적인 레퍼런스를 가질 필요도 없다. 사슬의 순서를 바꾸는 등 역할을 동적으로 추가하고 제거할 수도 있다.

 

 

Handler는 요청을 처리하는 인터페이스를 정의하고, Successor라고 불리는 후속 처리자와의 연결을 구현한다. 위 코드에서는 Node가 되겠다. Handler를 구현한 ConcreteHandler는 자기가 처리할 일이라면 처리하고 아니면 후속 처리자에게 요청을 보내 위임한다. 이렇게 알맞는 처리자를 찾을 때까지 이동하다가 처리된다.

 

 

 

 

 

* 아래의 자료들을 참고하였습니다.

에릭 프리먼 · 엘리자베스 롭슨 · 케이시 시에라 · 버트 베이츠, 『헤드퍼스트 디자인패턴 개정판』, 서환수 옮김, 한빛미디어(2022), p636-637.

 

디자인 패턴 - 책임 연쇄(Chain of Responsibility)

소프트웨어 아키텍처의 규모가 커지고, 개발자 간 업무를 분배하여 일을 진행하는 경우가 많아졌습니다. 혼자 기능을 전부 구현한다면, 편하게 코드를 작성해도 되지만, 여러 사람에게 일감을

it-techtree.tistory.com

 

댓글