책임 연쇄

정선호·2023년 5월 25일
0

Design Patterns

목록 보기
19/24

책임 연쇄 패턴

위키피디아 - 책임 연쇄
설명 및 스도코드

  • 핸들러들의 체인을 따라 요청을 전달할 수 있게 해주는 행동 디자인 패턴
    • 각 핸들러는 요청을 받으면 요청을 처리할지 아니면 자신의 소관이 아니므로 체인의 다음 핸들러로 전달할지 결정

책임 연쇄 패턴의 구조

  • 핸들러(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);
    }
}
profile
학습한 내용을 빠르게 다시 찾기 위한 저장소

0개의 댓글