Chain of Responsibility 패턴은 행동(Behavioral) 디자인 패턴 중 하나로, 요청을 보내는 객체(Client)와 이를 처리하는 객체(Handler) 사이의 결합도를 낮추는 데 목적이 있습니다. 이 패턴은 여러 개의 핸들러(Handler) 객체를 연결하여 하나의 체인(Chain)을 형성하고, 요청이 이 체인을 따라 순차적으로 전달되도록 합니다. 각 핸들러는 요청을 처리할 수 있는지 스스로 판단하며, 처리 가능한 경우 요청을 소비하고 체인을 종료합니다. 처리할 수 없는 경우 다음 핸들러로 요청을 전달합니다.
1. Chain of Responsibility 패턴의 동작 원리
이 패턴의 핵심은 요청의 발신자와 수신자를 분리하는 데 있습니다. 발신자는 특정 핸들러를 직접 호출하는 대신 체인의 첫 번째 핸들러에게 요청을 보내기만 하면 됩니다. 핸들러는 자신의 책임 범위를 확인하고, 요청을 처리하거나 다음 핸들러로 넘깁니다.
주요 구성 요소:
- Handler (핸들러 인터페이스/추상 클래스): 요청을 처리하는 메서드를 정의하고, 다음 핸들러에 대한 참조(Reference)를 유지합니다.
- ConcreteHandler (구체적인 핸들러): 실제 요청 처리 로직을 구현합니다. 자신이 처리할 수 있으면 처리하고, 그렇지 않으면 체인의 다음 핸들러에게 요청을 위임합니다.
- Client (클라이언트): 핸들러 체인을 구성하고, 체인의 첫 번째 핸들러에게 요청을 보냅니다.
2. Decorator 패턴 및 Iterator 패턴과의 비교
Chain of Responsibility 패턴은 다른 패턴과 구조적 유사성을 보이기도 하지만, 목적과 동작 방식에서 차이가 있습니다.
| 특징 | Chain of Responsibility | Decorator |
|---|---|---|
| 목적 | 요청 발신자와 수신자 간의 결합도 감소, 여러 핸들러가 요청을 순차적으로 처리 | 객체에 동적으로 새로운 책임(기능) 추가 |
| 구조 | 요청이 체인을 따라 전달되며, 각 핸들러는 다음 핸들러 참조를 가짐 | 원본 객체를 래핑(Wrapping)하여 기능을 확장 |
| 요청 전달 | 요청은 체인을 따라 전달되며, 특정 핸들러가 처리하거나 체인 끝까지 전달됨 | 요청 전달 없이, 래핑된 객체에 기능을 추가함 |
| 핵심 | 요청 처리 순서 및 중단 조건 관리 | 객체의 행위를 동적으로 확장 (OCP 원칙 준수) |
| 사용 예 | 로깅 시스템, 승인 워크플로우, 미들웨어 파이프라인 | 스트림 래퍼(IO Stream), UI 컴포넌트 기능 추가 |
3. 패턴의 장점과 단점
장점
- 결합도 감소: 요청 발신자는 특정 핸들러에 대한 의존성 없이 체인에 요청을 전송합니다.
- 유연성 및 확장성: 체인에 새로운 핸들러를 추가하거나 기존 핸들러의 순서를 변경하는 것이 용이합니다.
- 책임 분리 (SRP): 각 핸들러는 자신의 책임(특정 조건 검증 또는 처리)에만 집중할 수 있습니다.
- 동적 구성: 런타임에 핸들러 체인을 동적으로 구성할 수 있습니다.
단점
- 성능 저하 가능성: 체인이 길어지면 요청이 여러 핸들러를 거쳐야 하므로 성능에 영향을 줄 수 있습니다.
- 디버깅 복잡성: 어떤 핸들러가 요청을 처리했는지, 또는 처리되지 않았는지 추적하기 어려울 수 있습니다.
- 요청 미처리 가능성: 적절한 핸들러가 체인에 존재하지 않으면 요청이 처리되지 않고 종료될 수 있습니다.
4. 실제 적용 사례
Chain of Responsibility 패턴은 다양한 시스템에서 활용됩니다.
- 로깅 시스템: 로그 레벨(DEBUG, INFO, WARN, ERROR)에 따라 다른 핸들러가 로그를 처리하도록 구성할 수 있습니다.
- 승인 워크플로우: 팀장, 부장, 이사 순으로 승인 권한을 위임하는 시스템을 구현할 수 있습니다.
- 보안 인증 체인: HTTP 요청이 특정 리소스에 접근하기 전에 인증(Authentication), 권한 부여(Authorization), 입력 검증(Validation) 단계를 순차적으로 거치도록 구성할 수 있습니다.
- 이벤트 버블링 (JavaScript DOM): 이벤트가 발생한 요소에서 상위 요소로 전파되는 방식을 설명합니다.
5. Spring Boot 기반 구현 예제: 신용 평가 시스템
아래는 Spring 프레임워크를 사용하여 Chain of Responsibility 패턴을 구현한 예제입니다. 각 핸들러는 Spring Bean으로 관리되며, 체인은 자동으로 구성됩니다.
5.1. Handler 인터페이스 정의
public interface RuleHandler {
// 요청 처리 성공 시 true, 실패 시 false 반환
boolean evaluate(RequestContext request);
}
5.2. 요청 객체(Request Context) 정의
import lombok.Data;
@Data
public class RequestContext {
private String userId;
private Integer creditScore;
private Double loanAmount;
// 기타 필요한 필드
}
참고: Lombok의 @Data 어노테이션은 Getter, Setter, toString 등을 자동 생성합니다.
5.3. 구체적인 Rule 핸들러 구현
각 핸들러는 `@Component`와 `@Order` 어노테이션(또는 Ordered 인터페이스 구현)을 통해 순서를 지정할 수 있습니다. 아래 예제는 `@Order`를 사용합니다.
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(1)
public class IdentityValidationRule implements RuleHandler {
@Override
public boolean evaluate(RequestContext request) {
if (request.getUserId() == null || request.getUserId().isBlank()) {
System.err.println("[실패] 사용자 ID 확인: 유효하지 않음");
return false;
}
System.out.println("[성공] 사용자 ID 확인 완료");
return true;
}
}
@Component
@Order(2)
public class BlacklistCheckRule implements RuleHandler {
@Override
public boolean evaluate(RequestContext request) {
// 가상의 블랙리스트 사용자 ID
if ("blocked_user_01".equals(request.getUserId())) {
System.err.println("[실패] 블랙리스트 확인: 차단된 사용자");
return false;
}
System.out.println("[성공] 블랙리스트 확인 완료");
return true;
}
}
@Component
@Order(3)
public class CreditScoreRule implements RuleHandler {
@Override
public boolean evaluate(RequestContext request) {
if (request.getCreditScore() != null && request.getCreditScore() < 650) {
System.err.println("[실패] 신용 점수 확인: 기준 미달 (" + request.getCreditScore() + ")");
return false;
}
System.out.println("[성공] 신용 점수 확인 완료");
return true;
}
}
5.4. 핸들러 체인 관리자 (Chain Manager)
Spring을 사용하면, `List
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class RuleChainExecutor {
@Autowired
private List<RuleHandler> ruleHandlers; // Spring이 @Order 순서로 정렬하여 주입
public boolean executeChain(RequestContext request) {
for (RuleHandler handler : ruleHandlers) {
boolean isSuccess = handler.evaluate(request);
if (!isSuccess) {
System.out.println("요청 처리 실패, 체인 중단.");
return false; // 실패 시 체인 중단
}
}
System.out.println("모든 규칙 통과 완료!");
return true;
}
}
5.5. 클라이언트 실행 코드
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class ChainOfResponsibilityTest {
@Autowired
private RuleChainExecutor executor;
@Test
public void testChain() {
RequestContext req = new RequestContext();
req.setUserId("user_001");
req.setCreditScore(700);
req.setLoanAmount(10000.0);
executor.executeChain(req); // 기대: 성공
}
@Test
public void testChainFailure() {
RequestContext req = new RequestContext();
req.setUserId("blocked_user_01");
req.setCreditScore(600);
req.setLoanAmount(50000.0);
executor.executeChain(req); // 기대: 블랙리스트에서 실패
}
}
5월 24일 06:02에 게시됨