디자인 패턴 시리즈 10: 책임 연쇄 패턴 (Chain of Responsibility Pattern)
책임 연쇄 패턴은 여러 객체들이 순차적으로 요청을 처리할 기회를 가지는 패턴이다.
각 객체는 처리할 수 있는 범위에 따라 요청을 처리할 수 있고, 처리하지 못하면 다음 객체로 요청을 넘겨 처리하도록 한다.
이를 통해 객체 간의 결합도를 낮추고, 유연한 요청 처리 구조를 제공할 수 있다.
책임 연쇄 패턴의 필요성
보통 프로그램은 요청을 처리할 객체를 명확히 지정해야 하지만, 특정 객체에 요청을 고정시키면 유연성이 떨어진다.
예를 들어, 다양한 조건에 따라 여러 객체가 처리할 수 있는 상황이 있을 때, 요청을 처리할 객체를 고정시키면 코드가 복잡해지고 유지보수가 어려워질 수 있다.
책임 연쇄 패턴을 사용하면 다음과 같은 장점을 얻을 수 있다:
- 요청을 여러 객체들이 처리할 기회를 가질 수 있으며, 각 객체는 자신이 처리할 수 있는지 판단하여 처리할지, 다음 객체에게 넘길지를 결정한다.
- 객체 간의 결합도를 낮추고, 요청 처리 과정에서 발생할 수 있는 변경을 쉽게 대응할 수 있다.
- 유연한 요청 처리 흐름을 설계할 수 있다.
예시: 고객 지원 시스템
고객이 회사에 문의할 때, 문의 내용에 따라 다양한 부서에서 처리될 수 있다.
예를 들어, 일반적인 문의는 고객 상담 부서에서 처리하지만, 기술적인 문제는 기술 지원팀에서 처리하고, 재무 관련 문제는 재무팀에서 처리해야 한다.
이런 경우 책임 연쇄 패턴을 사용하면 각 부서가 요청을 처리할 수 있도록 연쇄적으로 연결하여, 각 부서가 적절히 문의를 처리하도록 만들 수 있다.
책임 연쇄 패턴의 구조
- Handler 인터페이스: 모든 처리 객체가 구현해야 하는 인터페이스로,
handleRequest()
메서드를 정의한다. 각 객체는 다음 처리 객체를 가리키는 참조를 갖고 있다. - ConcreteHandler: 요청을 처리하는 구체적인 처리 객체들이다. 자신이 요청을 처리할 수 있으면 처리하고, 처리하지 못하면 다음 객체로 요청을 넘긴다.
- Client: 요청을 처리할 객체 체인을 설정하고, 요청을 보내는 역할을 한다.
구조 다이어그램
Client
└─ Handler (인터페이스)
├─ ConcreteHandler1
├─ ConcreteHandler2
└─ ConcreteHandler3
└─ 처리할 객체가 없을 때 요청이 종료됨
책임 연쇄 패턴 동작 순서
- *라이언트(Client)는 요청을 처리할 첫 번째 핸들러 객체에게 요청을 보낸다.
- 핸들러(Handler)는 자신이 요청을 처리할 수 있는지 확인한 후, 처리할 수 있으면 처리하고, 그렇지 않으면 다음 핸들러로 요청을 넘긴다.
- 요청이 체인의 끝까지 전달되거나, *간에서 처리되면 요청이 종료된다.
책임 연쇄 패턴 예시 (Chain of Responsibility Pattern)
고객 문의를 처리하는 시스템을 예시로 책임 연쇄 패턴을 적용해 보겠다.
각 부서(일반 문의, 기술 지원, 재무팀)가 연쇄적으로 연결되어, 각 부서가 처리할 수 있는 범위 내에서 문의를 처리하거나, 다음 부서로 넘기게 된다.
Java로 책임 연쇄 패턴 구현하기
// Handler 인터페이스
abstract class SupportHandler {
protected SupportHandler nextHandler;
public void setNextHandler(SupportHandler nextHandler) {
this.nextHandler = nextHandler;
}
public abstract void handleRequest(String request);
}
// ConcreteHandler: 일반 문의 처리
class GeneralSupportHandler extends SupportHandler {
@Override
public void handleRequest(String request) {
if (request.equals("일반 문의")) {
System.out.println("일반 문의 처리: 고객 상담 부서에서 처리합니다.");
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
}
}
}
// ConcreteHandler: 기술 지원 처리
class TechnicalSupportHandler extends SupportHandler {
@Override
public void handleRequest(String request) {
if (request.equals("기술 지원")) {
System.out.println("기술 지원 처리: 기술 지원팀에서 처리합니다.");
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
}
}
}
// ConcreteHandler: 재무 문제 처리
class FinancialSupportHandler extends SupportHandler {
@Override
public void handleRequest(String request) {
if (request.equals("재무 문제")) {
System.out.println("재무 문제 처리: 재무팀에서 처리합니다.");
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
}
}
}
// 클라이언트 코드
public class Main {
public static void main(String[] args) {
// 핸들러 체인 설정
SupportHandler generalSupport = new GeneralSupportHandler();
SupportHandler technicalSupport = new TechnicalSupportHandler();
SupportHandler financialSupport = new FinancialSupportHandler();
generalSupport.setNextHandler(technicalSupport);
technicalSupport.setNextHandler(financialSupport);
// 요청 처리
generalSupport.handleRequest("일반 문의"); // 출력: 일반 문의 처리: 고객 상담 부서에서 처리합니다.
generalSupport.handleRequest("기술 지원"); // 출력: 기술 지원 처리: 기술 지원팀에서 처리합니다.
generalSupport.handleRequest("재무 문제"); // 출력: 재무 문제 처리: 재무팀에서 처리합니다.
}
}
코드 설명
- SupportHandler (Handler 인터페이스): 요청을 처리하거나, 처리하지 못하면 다음 핸들러로 요청을 넘긴다.
setNextHandler()
메서드를 통해 체인을 설정하고,handleRequest()
메서드에서 요청을 처리한다. - GeneralSupportHandler, TechnicalSupportHandler, FinancialSupportHandler (ConcreteHandler): 각각 일반 문의, 기술 지원, 재무 문제를 처리하는 구체적인 핸들러들이다.
- Main (Client): 핸들러 체인을 설정하고, 요청을 보내어 처리하는 클라이언트 코드이다.
출력 결과
일반 문의 처리: 고객 상담 부서에서 처리합니다.
기술 지원 처리: 기술 지원팀에서 처리합니다.
재무 문제 처리: 재무팀에서 처리합니다.
책임 연쇄 패턴의 장점
- 객체 간의 결합도 감소: 클라이언트는 어떤 객체가 요청을 처리할지 알 필요가 없으며, 요청을 처리할 객체들이 자동으로 결정된다.
- 유연한 요청 처리: 요청의 처리 순서와 흐름을 쉽게 변경하거나 확장할 수 있다. 새로운 요청 처리 객체를 추가하는 것도 매우 용이하다.
- 동적 체인 구성: 체인의 구성을 동적으로 변경할 수 있어 다양한 요청 처리 흐름을 유연하게 설계할 수 있다.
책임 연쇄 패턴의 단점
- 요청의 처리 지연: 체인에서 여러 객체를 거치며 요청이 처리되기 때문에, 처리 속도가 느려질 수 있다.
- 체인이 너무 길어질 수 있음: 체인이 너무 길어지면 처리할 객체들이 많아져 비효율적일 수 있다.
마무리
책임 연쇄 패턴은 여러 객체들이 요청을 처리할 기회를 가지는 패턴으로, 객체 간의 결합도를 줄이고 요청 처리 과정을 유연하게 만들 수 있다.
각 객체는 자신이 처리할 수 있는 요청만 처리하고, 그렇지 않으면 다음 객체로 넘기며, 클라이언트는 어느 객체가 요청을 처리할지 알 필요 없이 체인만 설정하면 된다.
아래 글에서 다른 디자인 패턴들을 확인할 수 있다.
디자인 패턴 모음