프로그래밍/디자인패턴

책임 연쇄 패턴(Chain of Responsibility) 총정리

Jinwookoh 2025. 12. 7. 18:08

"로그인 체크도 해야 하고, 권한 검사도 해야 하고, 로그도 남겨야 하고, 데이터 압축도 해야 하는데..."

클라이언트의 요청 하나를 처리하기 위해 여러 개의 처리 과정(Handler)을 거쳐야 할 때, 이를 사슬(Chain)처럼 엮어서 순차적으로 실행하게 만드는 것이 책임 연쇄 패턴입니다. 하지만 이 사슬을 어떻게 구성하느냐에 따라 동작 방식이 완전히 다릅니다.

Shutterstock
 

 


1. 방식 1: 순수 책임 연쇄 (Pure Chain) - "선착순 1명"

GoF 디자인 패턴에 나오는 고전적인 방식입니다. **"나 이거 처리 못 해? 그럼 다음 사람에게 넘겨"**라는 식입니다. 중요한 점은 누군가 처리를 완료하면 사슬은 거기서 멈춥니다.

상황 (지출 결재 시스템)

  • 팀장: 10만 원 이하 전결
  • 본부장: 100만 원 이하 전결
  • 사장: 제한 없음

코드 예시

Java
 
abstract class Approver {
    protected Approver nextApprover; // 다음 결재자

    public void setNext(Approver next) { this.nextApprover = next; }

    public void processRequest(int amount) {
        if (canApprove(amount)) {
            approve(amount); // 내가 처리하고 끝! (체인 중단)
        } else if (nextApprover != null) {
            nextApprover.processRequest(amount); // 난 못하니 다음 사람에게 토스
        } else {
            System.out.println("처리할 사람이 없습니다.");
        }
    }

    protected abstract boolean canApprove(int amount);
    protected abstract void approve(int amount);
}

// 사용
teamLeader.setNext(director);
director.setNext(ceo);

teamLeader.processRequest(500000); // 팀장 Pass -> 본부장 처리(끝)

특징

  • 배타적 처리: 핸들러 중 단 하나만 요청을 처리합니다.
  • 예: GUI 이벤트(버튼이 클릭 안 되면 부모 패널로 클릭 이벤트 전파).

2. 방식 2: 필터 체인 (Filter Chain / Interceptor) - "릴레이 경주"

웹 개발(Spring)에서 주로 쓰이는 변형된 방식입니다. 모든 핸들러가 요청을 건드리고 지나갑니다. **전처리(Pre)와 후처리(Post)**가 모두 가능합니다.

상황 (웹 요청 처리 파이프라인)

  • LoggingFilter: 요청 로그 남김
  • AuthFilter: 인증 토큰 검사
  • EncodingFilter: UTF-8 변환

코드 예시 (Servlet Filter 스타일)

Java
 
interface Filter {
    void doFilter(Request req, FilterChain chain);
}

class FilterChain {
    private List<Filter> filters = new ArrayList<>();
    private int index = 0;

    public void addFilter(Filter filter) { filters.add(filter); }

    public void doFilter(Request req) {
        if (index < filters.size()) {
            Filter filter = filters.get(index++);
            filter.doFilter(req, this); // 다음 필터를 호출하라고 체인(this)을 넘김
        }
    }
}

class LoggingFilter implements Filter {
    public void doFilter(Request req, FilterChain chain) {
        System.out.println("1. [전처리] 로그 기록");
        chain.doFilter(req); // 다음 사람에게 마이크 넘김 (여기서 멈추지 않음!)
        System.out.println("4. [후처리] 로그 종료");
    }
}

class AuthFilter implements Filter {
    public void doFilter(Request req, FilterChain chain) {
        System.out.println("2. [전처리] 인증 확인");
        if (req.isAuth()) {
            chain.doFilter(req); // 인증 성공 시에만 다음 진행
        }
        // 인증 실패하면 여기서 체인 끊김(반환)
    }
}

특징

  • 파이프라인 처리: 다수의 객체가 하나의 요청을 순서대로 가공합니다.
  • 제어권: 각 필터가 chain.doFilter()를 호출할지 말지 결정하여, 흐름을 계속 이을지 중단할지 통제합니다.

3. 실무 예시: 스프링 시큐리티 (Spring Security)

스프링 시큐리티는 거대한 Filter Chain 덩어리입니다.

SecurityFilterChain 내부에는 CsrfFilter, UsernamePasswordAuthenticationFilter 등 수십 개의 필터가 연결되어 있으며, 요청이 들어오면 이 필터들을 싹 훑고 지나가며 보안 검사를 수행합니다.


요약: Pure Chain vs Filter Chain

구분 Pure Chain (GoF) Filter Chain (Web)
처리 방식 책임 떠넘기기 (Pass the buck) 파이프라인 (Pipeline)
실행 핸들러 핸들러 중 하나만 실행됨 모든 핸들러가 순차적으로 실행됨
종료 시점 내가 처리 가능하면 즉시 종료 마지막 핸들러까지 갔다가 되돌아옴
대표 사례 결재 시스템, 예외 처리(try-catch) 서블릿 필터, 스프링 시큐리티, 인터셉터

결론

"이 요청을 누가 처리할지 모르겠으니, 처리할 수 있는 놈이 나올 때까지 돌려보자"라면 Pure Chain입니다.

반면, "이 요청이 도착하기 전에 검문소를 5개 통과시켜야 한다"라면 Filter Chain입니다.

웹 백엔드 개발자라면 사실상 Pure Chain보다 Filter Chain 패턴을 훨씬 더 많이 사용하고 분석하게 될 것입니다.