포스트

[Behavioral Pattern] 책임 연쇄 패턴 (Chain of Responsibility Pattern)

Design Pattern / Behavioral Pattern

책임 연쇄 패턴의 정의와 해당 디자인 패턴의 예제 코드를 통한 이해 및 설명 정리

개념

  • 객체들이 연결된 체인 형태로 존재하며, 각 객체가 요청을 처리하지 못할 경우, 다음 객체에게 책임을 전달하고, 다음 객체 또한 처리하지 못하면 다시 다음 객체로 전달하는 패턴

  • 객체 간의 결합도를 낮추고, 요청을 처리할 객체를 동적으로 결정하는 데 유용함

    • 요청 처리 과정에서 요청을 처리할 객체를 직접 지정하지 않아도 되므로, 클라이언트 코드와 서버 코드 간의 결합도가 낮아짐
  • 예를 들어 로그인 처리나 예외 처리와 같은 경우에 유용하게 사용될 수 있음

  • 요청 처리 과정에서 각 객체가 책임을 나누어 처리할 수 있으므로, 코드 유지 보수성과 확장성을 높일 수 있음

패턴 구조

chain_of_responsibility

  • Handler

    • 요청을 수신하고 처리 객체들의 집합을 정의하는 인터페이스
  • ConcreteHandler

    • 요청을 처리하는 실제 처리 객체

    • 핸들러에 대한 필드를 내부에 가지고 있으며, 메서드를 통해 다음 핸들러를 체인시키고 다음 체인의 핸들러를 바라봄

    • 자신이 처리할 수 없는 요구가 나오면, 바라보고 있는 다음 체인의 핸들러에게 요청을 떠넘김

    • ConcreteHandler_1 - ConcreteHandler_2 - ConcreteHandler_3 - … 이와 같은 방식으로 체인 형식이 구성됨

  • Client

    • 요청을 핸들러에게 전달

예제 코드

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
// 구체적인 핸들러를 묶는 인터페이스 (추상 클래스)
abstract class Handler {
  // 다음 체인으로 연결될 핸들러
  protected nextHandler: Handler | null = null;

  // 생성자를 통해 연결시킬 핸들러를 등록
  public setNext(handler: Handler): Handler {
    this.nextHandler = handler;
    return handler; // 메서드 체이닝 구성을 위해 인자를 그대로 반환함
  }

  // 자식 핸들러에서 구체화하는 추상 메서드
  protected abstract process(url: string): void;

  // 핸들러가 요청에 대해 처리하는 메서드
  public run(url: string): void {
    this.process(url);

    // 만일 핸들러가 연결된게 있다면 다음 핸들러로 책임을 떠넘김
    if (this.nextHandler !== null) {
      this.nextHandler.run(url);
    }
  }
}

export { Handler };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Handler } from "./handler";

class ProtocolHandler extends Handler {
  protected override process(url: string): void {
    const index: number = url.indexOf("://");

    if (index !== -1) {
      console.log(`PROTOCOL : ${url.substring(0, index)}`);
    } else {
      console.log("NO PROTOCOL");
    }
  }
}

export { ProtocolHandler };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Handler } from "./handler";

class DomainHandler extends Handler {
  protected override process(url: string): void {
    const startIndex: number = url.indexOf("://");
    const lastIndex: number = url.lastIndexOf(":");

    if (startIndex === -1) {
      if (lastIndex === -1) {
        console.log(`DOMAIN : ${url}`);
      } else {
        console.log(`DOMAIN : ${url.substring(0, lastIndex)}`);
      }
    } else if (startIndex !== lastIndex) {
      console.log(`DOMAIN : ${url.substring(startIndex + 3, lastIndex)}`);
    } else {
      console.log(`DOMAIN : ${url.substring(startIndex + 3)}`);
    }
  }
}

export { DomainHandler };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { Handler } from "./handler";

class PortHandler extends Handler {
  protected override process(url: string): void {
    const index: number = url.lastIndexOf(":");

    if (index !== -1) {
      const strPort: string = url.substring(index + 1);

      try {
        const port: number = Number.parseInt(strPort);
        console.log("PORT : " + port);
      } catch (error: any) {
        console.error(error);
      }
    }
  }
}

export { PortHandler };
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
import { ProtocolHandler } from "./concrete_handler_1";
import { DomainHandler } from "./concrete_handler_2";
import { PortHandler } from "./concrete_handler_3";
import { Handler } from "./handler";

class Client {
  public static main(_args?: string[]): void {
    // 1. 핸들러 생성
    const handler_1: Handler = new ProtocolHandler();
    const handler_2: Handler = new DomainHandler();
    const handler_3: Handler = new PortHandler();

    // 2. 핸들러 연결 설정 (handler_1 - handler_2 - handler_3)
    handler_1.setNext(handler_2).setNext(handler_3);

    // 3. 요청에 대한 처리 연쇄 실행
    const url_1 = "http://www.youtube.com:443";
    console.log(`INPUT : ${url_1}`);
    handler_1.run(url_1);

    console.log("");

    const url_2 = "https://hyungjinhan.vercel.app:3000";
    console.log(`INPUT : ${url_2}`);
    handler_1.run(url_2);

    console.log("");

    const url_3 = "http://localhost:8080";
    console.log(`INPUT : ${url_3}`);
    handler_1.run(url_3);
  }
}

Client.main();
// INPUT : http://www.youtube.com:443
// PROTOCOL : http
// DOMAIN : www.youtube.com
// PORT : 443

// INPUT : https://hyungjinhan.vercel.app:3000
// PROTOCOL : https
// DOMAIN : hyungjinhan.vercel.app
// PORT : 3000

// INPUT : http://localhost:8080
// PROTOCOL : http
// DOMAIN : localhost
// PORT : 8080

참고한 출처 사이트

Refactoring GURU

Inpa Dev Blog (디자인 패턴)

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.