책임 연쇄 패턴
위키피디아 - 책임 연쇄
설명 및 스도코드
- 핸들러들의 체인을 따라 요청을 전달할 수 있게 해주는 행동 디자인 패턴
- 각 핸들러는 요청을 받으면 요청을 처리할지 아니면 자신의 소관이 아니므로 체인의 다음 핸들러로 전달할지 결정
책임 연쇄 패턴의 구조
![](https://velog.velcdn.com/images/_hoya_/post/b64ad4bc-159a-49a7-892f-a55be489ccc3/image.png)
- 핸들러(Handler) 인터페이스
- 모든 구상 핸들러에 대한 공통적인 인터페이스
- 일반적으로는 요청을 처리하기 위한 단일 메서드만 포함되지만 때로는 체인의 다음 핸들러를 세팅하기 위한 다른 메서드가 있을 수 있다
- 기초 핸들러(Base Handler)
- 선택적 클래스, 모든 핸들러 클래스의 공통적 상용 코드를 작성할 수 있음
- 일반적으로 이 클래스는 다음 핸들러에 대한 참조를 저장하기 위한 필드 정의
- 혹은 디폴트 핸들러 행동을 구현할 수 있음
- 구상 핸들러(Concrete Handler)
- 요청을 처리하기 위한 실제 코드가 포함되어 있음
- 각 핸들러는 요청을 받으면 이 요청을 처리할지와 함께 체인을 따라 전달할지를 결정해야 한다
- 핸들러들은 일반적으로 자체 포함형이고 불변하며 생성자를 통해 필요한 모든 데이터를 한 번만 받는다
- 클라이언트
- 앱의 논리에 따라 체인들을 한 번만 구성하거나 동적으로 변경할 수 있다
- 요청은 체인의 모든 핸들러에 보낼 수 있으며 꼭 첫 번째 핸들러인 필요는 없다
책임 연쇄 패턴의 적용
- 프로그램이 다양한 방식으로 다양한 종류의 요청들을 처리할 것으로 예상되지만 정확한 요청 유형들과 순서들을 미리 알 수 없는 경우에 사용
- 여러 핸들러를 하나의 체인으로 연결하고, 요청을 받으면 각 핸들러에게 이 요청을 처리할 수 있는지 질문
- 특정 순서로 여러 핸들러를 실행해야 할 때
- 핸들러들의 집합과 그들의 순서가 런타임에 변경되어야 할 때
- 핸들러 클래스들 내부의 참조 필드에
setter
들을 제공하면 핸들러들은 동적으로 삽입, 제거 또는 재정렬될 수 있음
다른 패턴과의 관계
- 커맨드, 중재자, 옵서버 및 책임 연쇄 패턴은 요청의 발신자와 수신자를 연결하는 다양한 방법을 다룬다.
- 책임 연쇄 패턴은 잠재적 수신자의 동적 체인을 따라 수신자 중 하나에 의해 요청이 처리될 때까지 요청을 순차적으로 전달
- 커맨드 패턴은 발신자와 수신자 간의 단방향 연결을 설립
- 중재자 패턴은 발신자와 수신자 간의 직접 연결을 제거하여 그들이 중재자 객체를 통해 간접적으로 통신하도록 강제
- 옵서버 패턴은 수신자들이 요청들의 수신을 동적으로 구독 및 구독 취소할 수 있도록 한다
- 책임 연쇄 패턴은 종종 복합체 패턴과 함께 사용된다.
- 잎 컴포넌트가 요청을 받으면 해당 요청을 모든 부모 컴포넌트들의 체인을 통해 객체 트리의 루트까지 전달할 수 있다
- 책임 연쇄 패턴의 핸들러들은 커맨드로 구현할 수 있다
- 많은 다양한 작업을 같은 콘텍스트 객체에 대해 실행할 수 있으며, 해당 콘텍스트 객체는 요청의 역할을 한다. 여기에서의 요청은 처리 메서드의 매개변수를 의미
- 그러나 요청 자체가 커맨드 객체인 다른 접근방식이 존재한다. 이 방식 사용시 같은 작업을 체인에 연결된 일련의 서로 다른 콘텍스트들에서 실행할 수 있다
- 책임 연쇄 패턴과 데코레이터는 클래스 구조가 매우 유사하다. 두 패턴 모두 실행을 일련의 객체들을 통해 전달할 때 재귀적인 합성에 의존하나, 몇 가지 결정적인 차이점이 있다.
- 책임 연쇄 패턴 핸들러들은 서로 독립적으로 임의의 작업을 실행할 수 있으며, 또한 해당 요청을 언제든지 더 이상 전달하지 않을 수 있다
- 데코레이터들은 객체의 행동을 확장하며 동시에 이러한 행동을 기초 인터페이스와 일관되게 유지할 수 있다. 또한 데코레이터들은 요청의 흐름을 중단할 수 없다.
책임 연쇄 패턴 예시
// 구체적인 핸들러를 묶는 인터페이스 (추상 클래스)
abstract class Handler {
// 다음 체인으로 연결될 핸들러
protected Handler nextHandler = null;
// 생성자를 통해 연결시킬 핸들러를 등록
public Handler setNext(Handler handler) {
this.nextHandler = handler;
return handler; // 메서드 체이닝 구성을 위해 인자를 그대로 반환함
}
// 자식 핸들러에서 구체화 하는 추상 메서드
protected abstract void process(String url);
// 핸들러가 요청에 대해 처리하는 메서드
public void run(String url) {
process(url);
// 만일 핸들러가 연결된게 있다면 다음 핸들러로 책임을 떠넘긴다
if (nextHandler != null)
nextHandler.run(url);
}
}
class ProtocolHandler extends Handler {
@Override
protected void process(String url) {
int index = url.indexOf("://");
if (index != -1) {
System.out.println("PROTOCOL : " + url.substring(0, index));
} else {
System.out.println("NO PROTOCOL");
}
}
}
class DomianHandler extends Handler {
@Override
protected void process(String url) {
int startIndex = url.indexOf("://");
int lastIndex = url.lastIndexOf(":");
System.out.print("DOMAIN : ");
if (startIndex == -1) {
if (lastIndex == -1) {
System.out.println(url);
} else {
System.out.println(url.substring(0, lastIndex));
}
} else if (startIndex != lastIndex) {
System.out.println(url.substring(startIndex + 3, lastIndex));
} else {
System.out.println(url.substring(startIndex + 3));
}
}
}
class PortHandler extends Handler {
@Override
protected void process(String url) {
int index = url.lastIndexOf(":");
if (index != -1) {
String strPort = url.substring(index + 1);
try {
int port = Integer.parseInt((strPort));
System.out.println("PORT : " + port);
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
}
}
class Client {
public static void main(String[] args) {
// 1. 핸들러 생성
Handler handler1 = new ProtocolHandler();
Handler handler2 = new DomianHandler();
Handler handler3 = new PortHandler();
// 2. 핸들러 연결 설정 (handler1 → handler2 → handler3)
handler1.setNext(handler2).setNext(handler3);
// 3. 요청에 대한 처리 연쇄 실행
String url1 = "http://www.youtube.com:80";
System.out.println("INPUT: " + url1);
handler1.run(url1);
System.out.println();
String url2 = "https://www.inpa.tistory.com:443";
System.out.println("INPUT: " + url2);
handler1.run(url2);
System.out.println();
String url3 = "http://localhost:8080";
System.out.println("INPUT: " + url3);
handler1.run(url3);
}
}