책임 연쇄(Chain of Responsibility) 패턴
1개 요청을 2개 이상의 객체에서 처리해야 한다면 책임 연쇄 패턴을 사용한다.
책임 연쇄 패턴은 요청을 보내는 객체와 그 요청을 처리하는 객체 간의 결합을 느슨하게 만들기 위한 패턴이다.
요청이 여러 객체 사이에서 전달되는 과정에서 요청을 처리하는 객체를 동적으로 결정할 수 있게 하는 패턴이다.
객체들의 연결된 체인을 통해 요청이 전달되고, 각 객체는 자신이 처리할 수 있는지 여부를 판단하여 요청을 처리하거나 다음 객체로 전달할 수 있다.
이를 통해 요청을 보내는 객체는 어떤 객체가 실제로 요청을 처리하는 지 알 필요가 없게 되며, 처리할 객체들은 유연하게 추가되거나 제거될 수 있다.

책임 연쇄 패턴은 요청이 처리될 수 있는 적절한 처리자를 찾을 때까지 각 처리자에게 순차적으로 요청을 전달한다. 각 처리자는 요청을 처리하거나, 처리할 수 없으면 다음 처리자에게 전달하고, 이런 식으로 처인 상의 다양한 처리자가 처리할 수 있도록 구성된다.
구성 요소
- Handler(처리자) 인터페이스
- 요청을 처리하기 위한 메서드를 선언하는 인터페이스나 추상 클래스
- ConcreteHandler
- 실제 요청을 처리하는 구체적인 클래스로
Handler
인터페이스를 구현한다. - 자신이 처리할 수 없는 요청에 대해서는 다음 처리자로 요청을 전달하는 링크(체인)을 유지한다.
- Client(클라이언트)
- 요청을 시작하는 객체로, 첫 번째 처리자에게 요청을 보낸다.
예시: 상품 할인
DiscountHandler
인터페이스는 할인을 처리하는 메서드를 선언하고, TenPercentDiscountHandler
및 TwentyPercentDiscountHandler
는 실제로 할인을 처리하는 구체적인 클래스이다.
클라이언트는 먼저 TenPercentDiscountHandler
에게 할인을 적용하고, TwentyPercentDiscountHandler
로 전달되어 체인을 통해 적절한 할인이 적용된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
| // Handler 인터페이스
public interface DiscountHandler {
void applyDiscount(double amount);
void setNextHandler(DiscountHandler nextHandler);
}
// ConcreteHandler 클래스
public class TenPercentDiscountHandler implements DiscountHandler {
private DiscountHandler nextHandler;
@Override
public void applyDiscount(double amount) {
if (amount > 100) {
System.out.println("Applying 10% discount.");
amount *= 0.9;
System.out.println("Discounted amount: " + amount);
} else if (nextHandler != null) {
nextHandler.applyDiscount(amount);
}
}
@Override
public void setNextHandler(DiscountHandler nextHandler) {
this.nextHandler = nextHandler;
}
}
public class TwentyPercentDiscountHandler implements DiscountHandler {
private DiscountHandler nextHandler;
@Override
public void applyDiscount(double amount) {
if (amount > 200) {
System.out.println("Applying 20% discount.");
amount *= 0.8;
System.out.println("Discounted amount: " + amount);
} else if (nextHandler != null) {
nextHandler.applyDiscount(amount);
}
}
@Override
public void setNextHandler(DiscountHandler nextHandler) {
this.nextHandler = nextHandler;
}
}
// Client 클래스
public class Client {
public static void main(String[] args) {
// 처리자 생성
DiscountHandler handler1 = new TenPercentDiscountHandler();
DiscountHandler handler2 = new TwentyPercentDiscountHandler();
// 처리자 체인 구성
handler1.setNextHandler(handler2);
// 클라이언트가 요청
handler1.applyDiscount(150); // 예제에서는 20% 할인이 적용됨
}
}
|
특징
장점
- 요청을 보낸 쪽과 받는 쪽을 분리할 수 있다.
- 객체는 체인의 구조를 몰라도 되고, 그 체인에 들어있는 다른 객체들의 직접적인 레퍼런스를 가질 필요도 없으므로 객체를 단순하게 만들 수 있다.
- 사슬에 들어가는 객체를 바꾸거나 순서를 바꿈으로써 역할을 동적으로 추가하거나 제거할 수 있다.
단점
- 요청이 반드시 수행된다는 보장이 없다.
- 체인 끝에서도 처리되지 않을 수 있지만, 상황에 따라 장점이 될 수 있다.
- 실행 시에 과정을 살펴보거나 디버깅하기 힘들다.