역할 사슬 패턴은 요청을 처리할 수 있는 객체가 여러 개일 경우, 요청을 처리할 수 있는 객체들을 동적으로 연결해서 처리하도록 할 수 있는 행위 패턴이에요. 이 패턴은 이해가 쉽고 구조도 단순해서 글이 많이 짧을 것 같아요.

역할 사슬 패턴은 Handler, ConcreteHandler, Request 로 이루어져 있다고 보면되요. 각 클래스의 역할을 살펴봐요.
Handler (추상 클래스 또는 인터페이스)
역할: 요청을 처리하는 인터페이스에요. 다음 핸들러를 지정하는 setNext 메서드, 요청을 처리하는 handle 메서드가 있어요.
특징: 공통된 API(handle)을 제공하여, ConcreteHandler들이 따라야 할 규칙을 명확히 해줘요.
예시: 고객 문의 처리 시스템에서 "문의 처리 인터페이스"
ConcreteHandler (구상 핸들러)
역할: Handler를 구현하는 구상 클래스에요. 요청을 처리할 수 있으면 처리하고 아니면 next.handle(request)로 넘겨요.
특징: 자신이 처리할 수 있는 요청이면 처리, 아니면 다음 Handler로 넘겨요. (물론 실제로는 모두가 처리할 수도 있게 할 수도 있어요. 하지만 책에 나온대로 이해하기 쉽게 이렇게 설명하고 넘어갈게요.)
예시: 일반 문의 처리 담당자, 기술 지원 담당자, 관리자
Request
역할: 처리할 요청 객체에요. handler들이 이 request를 보고 처리 여부를 확인할 거에요.
특징: 핸들러에서 처리할 요청이에요.
예시: 일반 문의, 기술 문의

위 그림은 Client에서 request를 Handler 인터페이스로 처리하도록 요청할 경우 처리 되는 Flow Chart에요. 되게 쉽죠? 모든 요청에 대해 모든 핸들러가 처리할 수도 있어요. 하지만 어떤 핸들러에서 처리하면 다음 핸들러로 넘어가지 않도록 만들 수도 있어요. 목적에 맞게 적절히 사용하면 되요.
이메일 분류 기능을 역할 사슬 패턴으로 구현하는 예시를 보여주려고 해요. 핸들러가 스팸, 팬, 법적 메일을 처리해야하는데 각각에 대해 별도의 핸들러에서 전문적으로 처리하도록 만들거고, 체인을 구성하는 것을 보여줄 거에요.
// 이메일 요청 객체class Email { constructor(public subject: string, public body: string) {}}// 추상 Handlerabstract class EmailHandler { protected nextHandler: EmailHandler | null = null; setNext(handler: EmailHandler): EmailHandler { this.nextHandler = handler; return handler; // 체이닝 가능 } handle(email: Email): void { if (this.nextHandler) { this.nextHandler.handle(email); } else { console.log(`처리할 수 없는 이메일: ${email.subject}`); } }}// 스팸 메일 핸들러class SpamHandler extends EmailHandler { handle(email: Email): void { if (email.subject.includes("Win money") || email.body.includes("Free")) { console.log(`스팸 처리: ${email.subject}`); } else { super.handle(email); } }}// 팬 메일 핸들러class FanMailHandler extends EmailHandler { handle(email: Email): void { if (email.subject.includes("Fan letter") || email.body.includes("I love your work")) { console.log(`팬 메일 처리: ${email.subject}`); } else { super.handle(email); } }}// 법적 메일 핸들러class LegalHandler extends EmailHandler { handle(email: Email): void { if (email.subject.includes("Legal") || email.body.includes("Notice")) { console.log(`법적 메일 처리: ${email.subject}`); } else { super.handle(email); } }}// 체인 구성const spamHandler = new SpamHandler();const fanMailHandler = new FanMailHandler();const legalHandler = new LegalHandler();spamHandler.setNext(fanMailHandler).setNext(legalHandler);// 테스트const emails = [ new Email("Win money now!", "Free prize awaits you"), new Email("Fan letter from Alice", "I love your work!"), new Email("Legal Notice", "You are required to respond."), new Email("Random email", "Hello there")];emails.forEach(email => spamHandler.handle(email));요청을 처리할 인터페이스를 분리하고, 요청 처리 책임을 여러 객체로 분리할 수 있어요. 그리고 필요에 따라 동적으로 체인을 구성하여 처리할 수도 있죠.
장점
요청자와 처리자를 분리할 수 있어 결합도가 낮아요.
기존 코드 수정없이 새로운 핸들러 추가가 쉬워서 확장성이 좋아요.
체인을 동적으로 변경할 수 있어서 유연성이 좋아요.
단일 책임 원칙 준수하기 좋아요.
단점
체인의 관리가 힘들어요. (특히나 동적이라 디버깅도 힘들고, 체인이 길어도 힘들다.)
아무 핸들러도 요청을 받지 못하는 경우가 생길 수 있어요.(필터링 역할로 장점일 수도 있다.)
체인이 길면 성능 문제가 생길 수 있어요.
요청 사슬 패턴은 쉬워서 크게 쓸 내용이 없을 것 같아요. 이 패턴은 윈도우의 마우스 클릭이나 키보드 이벤트 처리에 많이 쓰인데요. 가끔 프론트엔드 코드를 보면 handleRequest같은 것을 봤을 것 같아요. 그것들도 역할 사슬 패턴을 활용하고 있을거에요. HTML DOM 이벤트 버블링만 봐도 이 패턴을 쓰고 있음을 직감적으로도 알 수 있어요. 저의 경우에는 에디터에서 여러 플러그인들이 사용되는데, 각 플러그인들이 입력값을 복붙 값이나 입력값 확인하는 방식도 역할 사슬 패턴을 쓰고 있는 것을 봤어요. 그래서 실제로 저도 예상치 못하게 저의 의도대로 동작하지 않고, 다른 플러그인이 낚아채서 처리하는 바람에 디버깅이 너무너무 힘들었던 경험이 있어요. 왜냐하면 누가 어떻게 구현했는지도 모르는 역할 사슬 때문에 나의 역할 사슬까지 요청이 못왔거든요 ㅜㅜ. 그래서 다 뒤져봐야했습니다!! 이제 진짜 책에 나오는 패턴 몇개 안남았네요. 그때 까지 화이팅입니다.