Chain of Responsibility (COR) Pattern 정리

테사벨로그·2025년 12월 3일

Design Pattern

목록 보기
17/19

1. 왜 COR Pattern이 생겨났는가?

문제 상황

// ❌ 나쁜 예: 요청 처리자가 고정된 코드
public class HelpSystem {
    public void handleHelp(String context) {
        // 구체적인 처리자에 직접 의존
        if (context.equals("button")) {
            buttonHelp.showHelp();
        } else if (context.equals("dialog")) {
            dialogHelp.showHelp();
        } else if (context.equals("application")) {
            appHelp.showHelp();
        }
    }
}

문제점:

  • 새로운 처리자를 추가하려면 코드 수정 필요
  • 요청을 보내는 객체가 처리자를 명시적으로 알아야 함 (강한 결합)
  • 실행 중에 처리 순서 변경 불가능
  • 처리자 추가/제거 시 클라이언트 코드 변경 필요

2. Handler VS ConcreteHandler

1. Handler (추상 핸들러)

  • "요청을 처리하거나 다음으로 넘기세요"
  • 요청 처리의 인터페이스 정의
  • successor 참조를 통해 체인 연결
abstract class Handler {
    protected Handler successor;  // 다음 처리자 참조
    
    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }
    
    public abstract void handleRequest(int request);
}

왜 Abstract Class인가?

  • successor 필드와 setSuccessor() 메서드는 모든 핸들러에서 동일하게 사용
  • 공통 구현을 제공하면서 handleRequest()는 서브클래스에서 구현하도록 강제

2. ConcreteHandler (구체적 핸들러)

  • "내가 처리할 수 있으면 처리하고, 아니면 다음으로 전달"
  • 자신이 담당하는 요청 범위 판단
  • 처리 불가 시 successor에게 자동 위임
class ConcreteHandler1 extends Handler {
    public void handleRequest(int request) {
        if (canHandle(request)) {
            // 직접 처리
        } else if (successor != null) {
            successor.handleRequest(request);  // 다음으로 전달
        }
    }
}

3. 왜 Abstract Class인가? (Interface가 아닌 이유)

Abstract Class를 사용하는 이유

  1. 공통 구현 존재

    // ✅ 모든 Handler가 동일하게 사용하는 코드
    protected Handler successor;
    
    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }
  2. 체인 연결 로직 재사용

    • successor 관리는 모든 ConcreteHandler에서 동일
    • 중복 코드 방지
  3. 상태(successor) 유지 필요

    • Interface는 상태를 가질 수 없음 (Java 8 이전 기준)
    • 다음 처리자 참조를 저장해야 함

Interface를 쓸 수도 있는 경우

// Java 8+ default method 활용 시
public interface Handler {
    Handler getSuccessor();
    void setSuccessor(Handler successor);
    
    default void passToNext(Request request) {
        if (getSuccessor() != null) {
            getSuccessor().handleRequest(request);
        }
    }
    
    void handleRequest(Request request);
}

4. COR Pattern 핵심 구조

Client → Handler1 → Handler2 → Handler3 → ... → null
              ↓          ↓           ↓
         처리 가능?   처리 가능?   처리 가능?
              ↓          ↓           ↓
         Yes: 처리   Yes: 처리    Yes: 처리
         No: 전달    No: 전달     No: 종료(미처리)

핵심 특징:

  • 요청 발신자는 누가 처리하는지 모름 (암묵적 수신자)
  • 체인 내 객체들이 순차적으로 요청 처리 기회를 가짐
  • 런타임에 체인 구성 변경 가능

5. 예시 코드

Step 1: Handler 정의 (Abstract Class)

// 추상 핸들러
abstract class Handler {
    protected Handler successor;  // 다음 처리자
    
    // 다음 처리자 설정
    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }
    
    // 요청 처리 (서브클래스에서 구현)
    public abstract void handleRequest(int request);
}

Step 2: ConcreteHandler 구현

// 0~9 범위 처리
class ConcreteHandler1 extends Handler {
    public void handleRequest(int request) {
        if (request >= 0 && request < 10) {
            System.out.println("Handler1이 처리: " + request);
        } else if (successor != null) {
            successor.handleRequest(request);  // 다음으로 전달
        }
    }
}

// 10~19 범위 처리
class ConcreteHandler2 extends Handler {
    public void handleRequest(int request) {
        if (request >= 10 && request < 20) {
            System.out.println("Handler2가 처리: " + request);
        } else if (successor != null) {
            successor.handleRequest(request);
        }
    }
}

// 20~29 범위 처리
class ConcreteHandler3 extends Handler {
    public void handleRequest(int request) {
        if (request >= 20 && request < 30) {
            System.out.println("Handler3이 처리: " + request);
        } else if (successor != null) {
            successor.handleRequest(request);
        }
    }
}

Step 3: 체인 구성 및 실행

public class MainApp {
    public static void main(String[] args) {
        // 핸들러 생성
        Handler h1 = new ConcreteHandler1();
        Handler h2 = new ConcreteHandler2();
        Handler h3 = new ConcreteHandler3();
        
        // 체인 연결: h1 → h2 → h3
        h1.setSuccessor(h2);
        h2.setSuccessor(h3);
        
        // 요청 처리 (항상 체인의 시작점에 요청)
        int[] requests = {2, 5, 14, 22, 18, 3, 27, 20};
        
        for (int request : requests) {
            h1.handleRequest(request);
        }
    }
}

출력 결과

Handler1이 처리: 2
Handler1이 처리: 5
Handler2가 처리: 14
Handler3이 처리: 22
Handler2가 처리: 18
Handler1이 처리: 3
Handler3이 처리: 27
Handler3이 처리: 20

6. 실전 예시: GUI 도움말 시스템

// 도움말 핸들러 추상 클래스
abstract class HelpHandler {
    protected HelpHandler successor;
    
    public void setSuccessor(HelpHandler successor) {
        this.successor = successor;
    }
    
    public abstract void handleHelp();
}

// 버튼 도움말 (가장 구체적)
class ButtonHelp extends HelpHandler {
    private boolean hasHelp;
    
    public ButtonHelp(boolean hasHelp) {
        this.hasHelp = hasHelp;
    }
    
    public void handleHelp() {
        if (hasHelp) {
            System.out.println("버튼 도움말: 이 버튼은 인쇄를 실행합니다.");
        } else if (successor != null) {
            successor.handleHelp();  // 도움말 없으면 상위로 전달
        }
    }
}

// 다이얼로그 도움말
class DialogHelp extends HelpHandler {
    public void handleHelp() {
        System.out.println("다이얼로그 도움말: 인쇄 설정 창입니다.");
    }
}

// 애플리케이션 도움말 (가장 일반적)
class ApplicationHelp extends HelpHandler {
    public void handleHelp() {
        System.out.println("앱 도움말: 일반적인 사용 방법입니다.");
    }
}

// 사용
public class HelpSystem {
    public static void main(String[] args) {
        // 체인: Button → Dialog → Application
        HelpHandler appHelp = new ApplicationHelp();
        HelpHandler dialogHelp = new DialogHelp();
        HelpHandler buttonHelp = new ButtonHelp(false);  // 버튼에 도움말 없음
        
        buttonHelp.setSuccessor(dialogHelp);
        dialogHelp.setSuccessor(appHelp);
        
        // 버튼에서 도움말 요청 → 다이얼로그 도움말 출력
        buttonHelp.handleHelp();
    }
}

7. 핵심 정리

COR Pattern의 구성

요소역할특징
Handler요청 처리 인터페이스 + successor 관리Abstract Class (공통 구현 제공)
ConcreteHandler실제 요청 처리 또는 전달자신의 처리 범위 판단
Client체인의 시작점에 요청 전송누가 처리하는지 모름

장점 (Benefits)

  • 송신자-수신자 분리: 요청자는 처리자를 몰라도 됨
  • 유연한 체인 구성: 런타임에 핸들러 추가/제거/순서 변경 가능
  • 단일 책임 원칙: 각 핸들러는 자신의 처리만 담당

단점 (Drawbacks)

  • 처리 보장 없음: 체인 끝까지 가도 처리 안 될 수 있음
  • 디버깅 어려움: 어떤 핸들러가 처리했는지 추적 어려움
  • 명시적 지정 불가: 클라이언트가 특정 핸들러 지정 불가

언제 사용하는가?

  • ✅ 여러 객체가 요청을 처리할 수 있고, 처리자가 미리 정해지지 않을 때
  • ✅ 요청 처리자를 동적으로 결정해야 할 때
  • ✅ 요청이 처리되지 않아도 괜찮을 때
  • ✅ GUI 이벤트 처리, 로깅 레벨, 인증/권한 체크 등

관련 패턴

패턴관계
Composite부모-자식 관계를 successor 체인으로 활용 가능
Command요청을 객체로 캡슐화하여 COR에서 전달
Observer둘 다 송신자-수신자 분리, 다른 트레이드오프

핵심 원칙

  • 암묵적 수신자: 요청 발신자는 누가 처리하는지 모름
  • 체인 구조: Handler들이 순차적으로 연결
  • 처리 또는 전달: 각 Handler는 처리하거나 다음으로 넘김
  • 느슨한 결합: 클라이언트와 처리자 간 의존성 제거
profile
다들 응원합니다.

0개의 댓글