Chain of responsibility pattern.
Behavioral Pattern 중 하나로 클라이언트의 요청이 들어왔을 때, 해당 요청을 어떤 서버가 처리할 수 있는지 사전에 모르는 상황에 사용하는 design pattern이다.
서버는 서버끼리 서로 chain 형식으로 연결되어 있다. 그래서 자신이 처리할 수 있는 요청이라면, 자신이 처리하고, 자신이 처리하지 못하는 요청이라면 연결되어 있는 다음 객체에게 문제를 넘김으로써 최종적으로 요청을 처리 할 수 있는 객체의 의해 처리가 가능하다 client의 요청을 처리할 수 있는 처리객체를 집합(Chain)으로 만들어 부여함으로 결합을 느슨하기 위해 만들어진 디자인 패턴이다.
Goals of Chain of Responsibility Pattern
- 둘 이상의 객체가 요청을 처리할 수 있는 기회를 제공함으로써 발신자와 수신자가 결합하는 것을 방지합니다.
- 책임이 할당되는 방법에 대한 지식으로부터 client를 격리합니다.
- 요청이 체인으로 전송됩니다. 요청이 해결될 때까지 요청은 서버 체인에 의해 처리됩니다.
When apply a Chain of Responsibility Pattern
- 요청의 발신자와 수신자를 분리하는 경우
- 요청을 처리할 수 있는 객체가 여러개일 때 그 중 하나에 요청을 보내려는 경우
- 코드에서 처리객체(handler)를 명시적으로 지정하고 싶지 않은 경우
즉, 책임 연쇄 패턴은 요청을 처리할 수 있는 객체가 여러 개이고 처리객체가 특정적이지 않을 경우 권장되는 패턴이다.
Chain of Responsibility Pattern 장점
- 결합도를 낮추며, 요청의 발신자와 수신자를 분리시킬 수 있다.
- client는 처리객체의 집합 내부의 구조를 알 필요가 없다.
- 집합 내의 처리 순서를 변경하거나 처리객체를 추가 또는 삭제할 수 있어 유연성이 향상된다.
- 새로운 요청에 대한 처리객체 생성이 매우 편리하다.
Chain of Responsibility Pattern 단점
- 충분한 디버깅을 거치지 않았을 경우 집합 내부에서 사이클이 발생할 수 있다.
- 디버깅 및 테스트가 쉽지 않다.
Chain of Responsibility Pattern 구조
- Handler
요청을 수신하고 처리 객체들(client)이 가져야할 집합에 전달하는 인터페이스를 정의
집합의 첫 번째 핸들러에 대한 정보만 가지고 있으며 그 이후의 핸들러에 대해서는 알 수 없다. - Concrete Handler
Handler 인터페이스 구현, 각자가 요청 종류에 따라 자신이 처리할 수 있는 부분을 구현. 실제로 요청을 처리하는 객체입니다. - Client
맨 처음 client에게 처리를 요구. 요청을 전달하는 클라이언트입니다.
예제 코드 - Yuki_Chain_Respon_Test
// Main.java
public class Main {
public static void main(String[] args) {
Support alice = new NoSupport("Alice"); // Always False
Support bob = new LimitSupport("Bob", 100); // Trouble Num < 2nd parameter(100) : True
Support charlie = new SpecialSupport("Charlie", 429); // Troble Num == 2nd parameter(429) : True
Support diana = new LimitSupport("Diana", 200); // Trouble Num < 2nd parameter(200) : True
Support elmo = new OddSupport("Elmo"); // Troble Num % 2 == 1, Odd Number : True
Support fred = new LimitSupport("Fred", 300); // Trouble Num < 2nd parameter(300) : True
alice.setNext(bob).setNext(charlie).setNext(diana).setNext(elmo).setNext(fred);
for (int i = 0; i < 500; i += 33) { // 33 단위
alice.support(new Trouble(i));
}
}
}
//--------------------------------------------------------------------------------------
// LimitSupport.java
public class LimitSupport extends Support {
private int limit;
public LimitSupport(String name, int limit) {
super(name);
this.limit = limit;
}
protected boolean resolve(Trouble trouble) {
if (trouble.getNumber() < limit) {
return true;
} else {
return false;
}
}
}
//--------------------------------------------------------------------------------------
// NoSupport.java
public class NoSupport extends Support {
public NoSupport(String name) {
super(name);
}
protected boolean resolve(Trouble trouble) {
return false;
}
}
//--------------------------------------------------------------------------------------
// OddSupport.java
public class OddSupport extends Support {
public OddSupport(String name) {
super(name);
}
protected boolean resolve(Trouble trouble) {
if (trouble.getNumber() % 2 == 1) {
return true;
} else {
return false;
}
}
}
//--------------------------------------------------------------------------------------
// SpecialSupport.java
public class SpecialSupport extends Support {
private int number;
public SpecialSupport(String name, int number) {
super(name);
this.number = number;
}
protected boolean resolve(Trouble trouble) {
if (trouble.getNumber() == number) {
return true;
} else {
return false;
}
}
}
//--------------------------------------------------------------------------------------
// Support.java
public abstract class Support {
private String name;
private Support next;
public Support(String name) {
this.name = name;
}
public Support setNext(Support next) {
this.next = next;
return next;
}
public final void support(Trouble trouble) {
if (resolve(trouble)) { // Troble 해결
done(trouble); // 성공
} else if (next != null) {
next.support(trouble); // 해결되지는 않았지만 연결된 chain이 남았다
} else {
fail(trouble); // 실패
}
}
public String toString() {
return "[" + name + "]";
}
protected abstract boolean resolve(Trouble trouble);
protected void done(Trouble trouble) {
System.out.println(trouble + " is resolved by " + this + ".");
}
protected void fail(Trouble trouble) {
System.out.println(trouble + " cannot be resolved.");
}
}
//--------------------------------------------------------------------------------------
// Troble.java
public class Trouble {
private int number;
public Trouble(int number) {
this.number = number;
}
public int getNumber() {
return number;
}
public String toString() {
return "[Trouble " + number + "]";
}
}
resolved라고 출력된 Troble Number는 chain을 통해 넘어가서 해결한 문제들이며, cannot be resolved는 chain으로 넘겨도 해결할 수 없었던 문제들이라는 의미이다.