"요청을 처리할 수 있는 객체를 찾을 때까지 전달하자"
// 문제 1: 처리자가 강하게 결합
public class SupportSystem {
public void handleRequest(Request request) {
if (request.getPriority() == Priority.LOW) {
juniorSupport.handle(request);
} else if (request.getPriority() == Priority.MEDIUM) {
seniorSupport.handle(request);
} else if (request.getPriority() == Priority.HIGH) {
manager.handle(request);
} else {
ceo.handle(request);
}
// 모든 처리자를 직접 알아야 함!
}
}
// 문제 2: 처리 순서 변경이 어려움
public class RequestHandler {
public void process(Request request) {
// 1. 인증 확인
if (!authCheck(request)) return;
// 2. 권한 확인
if (!permissionCheck(request)) return;
// 3. 로깅
log(request);
// 4. 비즈니스 로직
execute(request);
// 순서를 바꾸거나 단계를 추가하려면 코드 수정!
}
}
// 문제 3: 여러 처리자 시도
public class PaymentProcessor {
public void pay(Payment payment) {
try {
creditCardProcessor.process(payment);
} catch (Exception e) {
try {
paypalProcessor.process(payment);
} catch (Exception e2) {
try {
bankTransferProcessor.process(payment);
} catch (Exception e3) {
System.out.println("모든 결제 실패");
}
}
}
// try-catch 지옥!
}
}
// 문제 4: 동적 처리자 추가 불가
public class Logger {
public void log(String message, Level level) {
if (level == Level.DEBUG) {
debugLogger.log(message);
} else if (level == Level.INFO) {
infoLogger.log(message);
} else if (level == Level.ERROR) {
errorLogger.log(message);
}
// 새 로그 레벨 추가? 코드 수정!
}
}- 강한 결합: 요청자가 모든 처리자를 알아야 함
- 순서 변경 어려움: 처리 순서가 코드에 고정
- 확장 어려움: 새 처리자 추가 시 코드 수정
- 책임 분리 불명확: 누가 처리할지 모호
요청을 보내는 객체와 받는 객체를 분리하고, 여러 객체에게 요청을 처리할 기회를 주는 패턴. 요청을 처리할 수 있는 객체를 찾을 때까지 체인을 따라 전달한다.
- 디커플링: 요청자와 처리자 분리
- 동적 체인: 런타임에 체인 구성
- 책임 분산: 여러 객체가 처리 기회
- 유연성: 순서 변경 용이
// Before: 직접 호출
if (condition1) {
handler1.handle();
} else if (condition2) {
handler2.handle();
}
// After: 체인으로 전달
handler1.setNext(handler2).setNext(handler3);
handler1.handle(request); // 체인을 따라 전달!┌─────────────┐
│ Client │
└─────────────┘
│
│ sends request
▼
┌──────────────────┐
│ Handler │ ← 추상 핸들러
├──────────────────┤
│ - next: Handler │───┐
│ + setNext() │ │ chain
│ + handleRequest()│ │
└──────────────────┘ │
△ │
│ extends │
│ │
┌──────────────────┐ │
│ConcreteHandler1 │ │
├──────────────────┤ │
│+ handleRequest() │◄──┘
└──────────────────┘
│
│ has next
▼
┌──────────────────┐
│ConcreteHandler2 │
├──────────────────┤
│+ handleRequest() │
└──────────────────┘
| 요소 | 역할 | 예시 |
|---|---|---|
| Handler | 추상 핸들러 | SupportHandler |
| ConcreteHandler | 구체적 핸들러 | JuniorSupport |
| Client | 요청 발송 | Customer |
| Request | 요청 객체 | SupportRequest |
/**
* Request: 지원 요청
*/
public class SupportRequest {
private String description;
private Priority priority;
public enum Priority {
LOW, MEDIUM, HIGH, CRITICAL
}
public SupportRequest(String description, Priority priority) {
this.description = description;
this.priority = priority;
}
public String getDescription() {
return description;
}
public Priority getPriority() {
return priority;
}
}
/**
* Handler: 추상 핸들러
*/
public abstract class SupportHandler {
protected SupportHandler next;
public SupportHandler setNext(SupportHandler next) {
this.next = next;
return next; // 체이닝을 위해
}
public void handle(SupportRequest request) {
if (canHandle(request)) {
processRequest(request);
} else if (next != null) {
System.out.println(" ↪️ " + getHandlerName() + " → 다음 핸들러로 전달");
next.handle(request);
} else {
System.out.println(" ❌ 처리할 수 있는 핸들러가 없습니다");
}
}
protected abstract boolean canHandle(SupportRequest request);
protected abstract void processRequest(SupportRequest request);
protected abstract String getHandlerName();
}
/**
* ConcreteHandler 1: 주니어 지원
*/
public class JuniorSupport extends SupportHandler {
@Override
protected boolean canHandle(SupportRequest request) {
return request.getPriority() == SupportRequest.Priority.LOW;
}
@Override
protected void processRequest(SupportRequest request) {
System.out.println(" 👨💼 주니어 지원: " + request.getDescription() + " 처리 완료");
}
@Override
protected String getHandlerName() {
return "주니어 지원";
}
}
/**
* ConcreteHandler 2: 시니어 지원
*/
public class SeniorSupport extends SupportHandler {
@Override
protected boolean canHandle(SupportRequest request) {
return request.getPriority() == SupportRequest.Priority.MEDIUM;
}
@Override
protected void processRequest(SupportRequest request) {
System.out.println(" 👨💻 시니어 지원: " + request.getDescription() + " 처리 완료");
}
@Override
protected String getHandlerName() {
return "시니어 지원";
}
}
/**
* ConcreteHandler 3: 매니저
*/
public class ManagerSupport extends SupportHandler {
@Override
protected boolean canHandle(SupportRequest request) {
return request.getPriority() == SupportRequest.Priority.HIGH;
}
@Override
protected void processRequest(SupportRequest request) {
System.out.println(" 👔 매니저: " + request.getDescription() + " 처리 완료");
}
@Override
protected String getHandlerName() {
return "매니저";
}
}
/**
* ConcreteHandler 4: CEO
*/
public class CEOSupport extends SupportHandler {
@Override
protected boolean canHandle(SupportRequest request) {
return request.getPriority() == SupportRequest.Priority.CRITICAL;
}
@Override
protected void processRequest(SupportRequest request) {
System.out.println(" 🎩 CEO: " + request.getDescription() + " 직접 처리!");
}
@Override
protected String getHandlerName() {
return "CEO";
}
}
/**
* 사용 예제
*/
public class ChainOfResponsibilityExample {
public static void main(String[] args) {
// 체인 구성
SupportHandler junior = new JuniorSupport();
SupportHandler senior = new SeniorSupport();
SupportHandler manager = new ManagerSupport();
SupportHandler ceo = new CEOSupport();
junior.setNext(senior)
.setNext(manager)
.setNext(ceo);
System.out.println("=== 고객 지원 시스템 ===\n");
// 다양한 우선순위의 요청
System.out.println("📞 요청 1: LOW");
junior.handle(new SupportRequest("로그인 문의", SupportRequest.Priority.LOW));
System.out.println("\n📞 요청 2: MEDIUM");
junior.handle(new SupportRequest("결제 오류", SupportRequest.Priority.MEDIUM));
System.out.println("\n📞 요청 3: HIGH");
junior.handle(new SupportRequest("시스템 장애", SupportRequest.Priority.HIGH));
System.out.println("\n📞 요청 4: CRITICAL");
junior.handle(new SupportRequest("보안 침해", SupportRequest.Priority.CRITICAL));
}
}실행 결과:
=== 고객 지원 시스템 ===
📞 요청 1: LOW
👨💼 주니어 지원: 로그인 문의 처리 완료
📞 요청 2: MEDIUM
↪️ 주니어 지원 → 다음 핸들러로 전달
👨💻 시니어 지원: 결제 오류 처리 완료
📞 요청 3: HIGH
↪️ 주니어 지원 → 다음 핸들러로 전달
↪️ 시니어 지원 → 다음 핸들러로 전달
👔 매니저: 시스템 장애 처리 완료
📞 요청 4: CRITICAL
↪️ 주니어 지원 → 다음 핸들러로 전달
↪️ 시니어 지원 → 다음 핸들러로 전달
↪️ 매니저 → 다음 핸들러로 전달
🎩 CEO: 보안 침해 직접 처리!
/**
* LogLevel
*/
public enum LogLevel {
DEBUG(1), INFO(2), WARNING(3), ERROR(4);
private int level;
LogLevel(int level) {
this.level = level;
}
public int getLevel() {
return level;
}
}
/**
* Handler: 로거
*/
public abstract class Logger {
protected LogLevel level;
protected Logger next;
public Logger(LogLevel level) {
this.level = level;
}
public Logger setNext(Logger next) {
this.next = next;
return next;
}
public void log(String message, LogLevel messageLevel) {
if (messageLevel.getLevel() >= level.getLevel()) {
write(message);
}
if (next != null) {
next.log(message, messageLevel);
}
}
protected abstract void write(String message);
}
/**
* ConcreteHandler: 콘솔 로거
*/
public class ConsoleLogger extends Logger {
public ConsoleLogger(LogLevel level) {
super(level);
}
@Override
protected void write(String message) {
System.out.println("🖥️ Console: " + message);
}
}
/**
* ConcreteHandler: 파일 로거
*/
public class FileLogger extends Logger {
public FileLogger(LogLevel level) {
super(level);
}
@Override
protected void write(String message) {
System.out.println("📄 File: " + message);
}
}
/**
* ConcreteHandler: 이메일 로거
*/
public class EmailLogger extends Logger {
public EmailLogger(LogLevel level) {
super(level);
}
@Override
protected void write(String message) {
System.out.println("📧 Email: " + message);
}
}
/**
* 사용 예제
*/
public class LoggerChainExample {
public static void main(String[] args) {
// 체인 구성: Console(DEBUG) → File(INFO) → Email(ERROR)
Logger console = new ConsoleLogger(LogLevel.DEBUG);
Logger file = new FileLogger(LogLevel.INFO);
Logger email = new EmailLogger(LogLevel.ERROR);
console.setNext(file).setNext(email);
System.out.println("=== 로거 체인 ===\n");
System.out.println("📝 DEBUG 메시지:");
console.log("디버그 정보", LogLevel.DEBUG);
System.out.println("\n📝 INFO 메시지:");
console.log("일반 정보", LogLevel.INFO);
System.out.println("\n📝 ERROR 메시지:");
console.log("심각한 에러 발생!", LogLevel.ERROR);
}
}실행 결과:
=== 로거 체인 ===
📝 DEBUG 메시지:
🖥️ Console: 디버그 정보
📝 INFO 메시지:
🖥️ Console: 일반 정보
📄 File: 일반 정보
📝 ERROR 메시지:
🖥️ Console: 심각한 에러 발생!
📄 File: 심각한 에러 발생!
📧 Email: 심각한 에러 발생!
/**
* Request
*/
public class HttpRequest {
private String method;
private String url;
private String authToken;
private Map<String, String> headers;
public HttpRequest(String method, String url) {
this.method = method;
this.url = url;
this.headers = new HashMap<>();
}
public void setAuthToken(String token) {
this.authToken = token;
}
public String getAuthToken() {
return authToken;
}
public String getMethod() {
return method;
}
public String getUrl() {
return url;
}
}
/**
* Handler: 미들웨어
*/
public abstract class Middleware {
protected Middleware next;
public Middleware setNext(Middleware next) {
this.next = next;
return next;
}
public boolean handle(HttpRequest request) {
if (!check(request)) {
return false;
}
if (next == null) {
return true;
}
return next.handle(request);
}
protected abstract boolean check(HttpRequest request);
}
/**
* ConcreteHandler: 인증 미들웨어
*/
public class AuthenticationMiddleware extends Middleware {
@Override
protected boolean check(HttpRequest request) {
System.out.println("🔐 인증 확인 중...");
if (request.getAuthToken() == null) {
System.out.println(" ❌ 인증 실패: 토큰 없음");
return false;
}
System.out.println(" ✅ 인증 성공");
return true;
}
}
/**
* ConcreteHandler: 권한 미들웨어
*/
public class AuthorizationMiddleware extends Middleware {
@Override
protected boolean check(HttpRequest request) {
System.out.println("🛡️ 권한 확인 중...");
// 간단히 admin만 DELETE 허용
if (request.getMethod().equals("DELETE") &&
!request.getAuthToken().contains("admin")) {
System.out.println(" ❌ 권한 없음");
return false;
}
System.out.println(" ✅ 권한 확인");
return true;
}
}
/**
* ConcreteHandler: 로깅 미들웨어
*/
public class LoggingMiddleware extends Middleware {
@Override
protected boolean check(HttpRequest request) {
System.out.println("📝 요청 로깅:");
System.out.println(" " + request.getMethod() + " " + request.getUrl());
return true; // 항상 통과
}
}
/**
* ConcreteHandler: Rate Limiting 미들웨어
*/
public class RateLimitMiddleware extends Middleware {
private int requestCount = 0;
private static final int MAX_REQUESTS = 3;
@Override
protected boolean check(HttpRequest request) {
System.out.println("⏱️ Rate Limit 확인 중...");
requestCount++;
if (requestCount > MAX_REQUESTS) {
System.out.println(" ❌ 요청 한도 초과 (" + requestCount + "/" + MAX_REQUESTS + ")");
return false;
}
System.out.println(" ✅ 요청 허용 (" + requestCount + "/" + MAX_REQUESTS + ")");
return true;
}
}
/**
* 사용 예제
*/
public class MiddlewareExample {
public static void main(String[] args) {
// 미들웨어 체인 구성
Middleware chain = new RateLimitMiddleware();
chain.setNext(new AuthenticationMiddleware())
.setNext(new AuthorizationMiddleware())
.setNext(new LoggingMiddleware());
System.out.println("=== HTTP 미들웨어 체인 ===\n");
// 요청 1: 정상
System.out.println("📡 요청 1: GET /users");
HttpRequest request1 = new HttpRequest("GET", "/users");
request1.setAuthToken("user-token");
boolean result1 = chain.handle(request1);
System.out.println("결과: " + (result1 ? "✅ 성공" : "❌ 실패"));
// 요청 2: 권한 없음
System.out.println("\n📡 요청 2: DELETE /users/1");
HttpRequest request2 = new HttpRequest("DELETE", "/users/1");
request2.setAuthToken("user-token");
boolean result2 = chain.handle(request2);
System.out.println("결과: " + (result2 ? "✅ 성공" : "❌ 실패"));
// 요청 3: 인증 실패
System.out.println("\n📡 요청 3: GET /products");
HttpRequest request3 = new HttpRequest("GET", "/products");
boolean result3 = chain.handle(request3);
System.out.println("결과: " + (result3 ? "✅ 성공" : "❌ 실패"));
}
}/**
* Request: 구매 요청
*/
public class PurchaseRequest {
private String item;
private double amount;
public PurchaseRequest(String item, double amount) {
this.item = item;
this.amount = amount;
}
public String getItem() {
return item;
}
public double getAmount() {
return amount;
}
}
/**
* Handler: 승인자
*/
public abstract class Approver {
protected Approver next;
protected double approvalLimit;
public Approver(double approvalLimit) {
this.approvalLimit = approvalLimit;
}
public Approver setNext(Approver next) {
this.next = next;
return next;
}
public void approve(PurchaseRequest request) {
if (request.getAmount() <= approvalLimit) {
processApproval(request);
} else if (next != null) {
System.out.println(" ↪️ 상위 결재자에게 전달");
next.approve(request);
} else {
System.out.println(" ❌ 승인 거부: 금액 초과");
}
}
protected abstract void processApproval(PurchaseRequest request);
}
/**
* ConcreteHandler: 팀장
*/
public class TeamLeader extends Approver {
public TeamLeader() {
super(1000); // $1,000까지 승인
}
@Override
protected void processApproval(PurchaseRequest request) {
System.out.println(" 👨💼 팀장 승인: " + request.getItem() +
" ($" + request.getAmount() + ")");
}
}
/**
* ConcreteHandler: 부서장
*/
public class DepartmentHead extends Approver {
public DepartmentHead() {
super(5000); // $5,000까지 승인
}
@Override
protected void processApproval(PurchaseRequest request) {
System.out.println(" 👔 부서장 승인: " + request.getItem() +
" ($" + request.getAmount() + ")");
}
}
/**
* ConcreteHandler: CEO
*/
public class CEO extends Approver {
public CEO() {
super(Double.MAX_VALUE); // 무제한
}
@Override
protected void processApproval(PurchaseRequest request) {
System.out.println(" 🎩 CEO 승인: " + request.getItem() +
" ($" + request.getAmount() + ")");
}
}
/**
* 사용 예제
*/
public class ApprovalChainExample {
public static void main(String[] args) {
// 승인 체인
Approver teamLeader = new TeamLeader();
Approver deptHead = new DepartmentHead();
Approver ceo = new CEO();
teamLeader.setNext(deptHead).setNext(ceo);
System.out.println("=== 구매 승인 시스템 ===\n");
System.out.println("📝 요청 1: $500");
teamLeader.approve(new PurchaseRequest("노트북", 500));
System.out.println("\n📝 요청 2: $3000");
teamLeader.approve(new PurchaseRequest("서버", 3000));
System.out.println("\n📝 요청 3: $10000");
teamLeader.approve(new PurchaseRequest("사무실", 10000));
}
}| 장점 | 설명 | 예시 |
|---|---|---|
| 디커플링 | 요청자와 처리자 분리 | 고객-지원팀 |
| 동적 체인 | 런타임에 체인 변경 | 미들웨어 추가 |
| 단일 책임 | 각 핸들러 독립적 | 인증/권한 분리 |
| 순서 제어 | 처리 순서 유연 | 로거 체인 |
| 단점 | 설명 | 해결책 |
|---|---|---|
| 처리 보장 없음 | 끝까지 전달 가능 | 기본 핸들러 |
| 디버깅 어려움 | 체인 추적 힘듦 | 로깅 강화 |
| 성능 오버헤드 | 여러 핸들러 거침 | 필요시에만 |
// 잘못된 예
handler1.setNext(handler2);
handler2.setNext(handler1); // 순환!
// 무한 루프 발생!해결:
// 체인 검증
public Approver setNext(Approver next) {
if (isInChain(next)) {
throw new IllegalArgumentException("순환 체인!");
}
this.next = next;
return next;
}✅ Handler 추상 클래스
✅ setNext() 메서드
✅ handle() 메서드
✅ ConcreteHandler 구현
✅ 체인 구성
| 상황 | 추천도 | 이유 |
|---|---|---|
| 다단계 처리 | ⭐⭐⭐ | 순차 처리 |
| 동적 처리자 | ⭐⭐⭐ | 런타임 변경 |
| 미들웨어 | ⭐⭐⭐ | HTTP 파이프라인 |
| 승인 시스템 | ⭐⭐⭐ | 계층적 승인 |
- 요청을 체인으로
- 처리자 독립적
- 동적 구성 가능
- 순서 중요