dh_0e

[Design Pattern] Behavior Design Patterns I (Chain of Responsibility Pattern, Command Pattern) 본문

Software Analysis & Design/Design Patterns

[Design Pattern] Behavior Design Patterns I (Chain of Responsibility Pattern, Command Pattern)

dh_0e 2025. 11. 17. 17:41

Behavior Design Patterns(행동 패턴)

  • 한 객체가 수행할 수 없는 작업여러 개의 객체로 어떻게 분배할지, 그렇게 하면서도 객체 사이의 결합도를 최소화하는 것에 중점을 둔 패턴

 

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

  • 클라이언트의 요청에 대한 세세한 처리를 한 객체가 전부 하는 것이 아닌,
    여러 개의 처리 객체들로 나누고 이들을 사슬(chain)처럼 연결해 연쇄적으로 처리하는 행동 패턴
    • Handler(핸들러): 처리 객체를 지칭
      • 요청을 받으면 각 핸들러는 요청을 처리할 수 있는지 판단하고, 없으면 다음 핸들러로 책임을 전가

Chain of Handlers

  • 필요한 상황
    • 온라인 주문 시스템을 개발하려고 하는 예시에서,
      • 인증된 사용자들만 주문을 생성할 수 있도록 시스템에 대한 접근을 제한
      • 관리 권한이 있는 사용자들에게 모든 주문에 대한 전체 접근 권한을 부여
    • 이러한 검사들을 차례대로 수행하도록 구성했다고 가정

  • 검사 조건을 추가하는 상황
    • 요청 내의 데이터를 정제(sanitize)하는 유효성 검사 단계를 추가
    • 시스템이 무차별 대입 공격을 방어하기 위해 같은 IP 주소에서 오는 반복적으로 실패한 요청을 걸러내는 검사를 즉시 추가
    • 데이터가 포함된 반복 요청에 대해 캐시된 결과를 반환하여 시스템 속도를 개선

 

  • 책임 연쇄 패턴의 아이디어
    • 특정 행동들을 핸들러라는 독립 실행형 객체로 변환
    • 핸들러를 체인으로 연결해서 체인을 따라 요청을 처리할 수 있도록 함
    • 경우에 따라서는 핸들러가 요청을 체인 뒤로 더 이상 전달하지 않고, 추가 처리를 중지하는 결정을 할 수도 있음

 

책임 연쇄 패턴의 구조

CoR Pattern 구조

구성 요소 역할 상세 설명
1. Handler 공통 인터페이스 모든 핸들러가 구현해야 할 공통적인 인터페이스를 선언
2. Base Handler 추상 클래스 모든 핸들러 클래스에 공통적인 상용구 코드를 포함하며, 다음 핸들러 참조를 위한 next 필드 존재
3. Concrete Handler 실제 처리 구현 요청을 처리하기 위한 실제 코드를 포함
4. 클라이언트 (Client) 체인 구성 및 요청 전송 애플리케이션의 논리에 따라 체인을 한 번만 구성하거나 동적으로 구성

 

ex)

url 파싱 시스템을 제작한다고 가정
하나의 Handler가 모든 파싱 처리(pattern 없음)
CoR Pattern's Example (Base Handler)
CoR Pattern's Example (Concrete Handlers)
CoR Pattern's Example (Concrete Handler & Client)

 

CoR Pattern 사용 시기

  • 특정 요청을 2개 이상의 여러 객체(Handler)에서 판별하고 처리해야 하는 경우
  • 특정 순서로 여러 핸들러를 실행해야 하는 경우
  • 프로그램이 다양한 방식과 종류의 요청을 처리할 것으로 예상되나, 요청 유형과 순서를 미리 알 수 없는 경우
  • 요청을 처리할 수 있는 객체 집합이 동적(런타임)으로 정의되어야 하는 경우

 

CoR Pattern 장단점

  • 장점
    • 클라이언트는 처리 객체(handler)의 체인 집합 내부의 구조를 알 필요가 없음 (Black Box)
    • SRP 준수: 각각의 체인(handler)은 자신의 해야하는 일만 하기 때문에 새로운 요청 처리에 유연하게 확장 가능
    • OCP 준수: 클라이언트 코드를 변경하지 않고 체인(handler)을 동적으로 변경 가능
      • main의 체인 구성 코드만 변경하면 됨
        • ? main 코드 수정은 client 코드 수정한 거 아냐? 그럼 OCP 준수가 아니잖아!
        • >> OCP 준수 맞음

클라이언트 코드 수정은 로직 사용을 담당하는 코드 수정을 말함

  • 단점
    • 실행 시에 코드의 흐름이 많아져서 과정을 한눈에 살펴보기가 복잡함
    • 무한 사이클로 체인이 구성되면 무한 루프에 빠질 수 있음
    • 책임 연쇄로 인한 처리 지연 문제가 발생할 수 있음

 

 


 

명령 패턴(Command Pattern)

  • 정의: 요청을 객체의 형태로 캡슐화하여 사용자가 보낸 요청을 나중에 이용할 수 있도록 재사용성을 높인 행동 패턴
    • 요청을 보내는 쪽(invoker)요청을 받는 쪽(reciever)을 커맨드를 이용하여 디커플링(Decoupling)하여 재사용을 높이고자 함
    • ex) 리모컨(Invoker)의 버튼을 누르면, 그 버튼은 "TV 켜기 명령"이라는 종이를 TV(Receiver)에게 전달 / 리모컨은 TV를 어떻게 켜는지(내부 회로) 몰라도 되며, TV, 에어컨, 오디오 등 어떤 기기에도 똑같은 press() 버튼을 사용할 수 있음

invoker와 reciever를 분리

  • 필요한 상황
    • 텍스트 편집기 앱을 개발하고 있는 상황에서, 편집기의 다양한 작업을 위한 여러 버튼이 있는 도구 모음(toorbar)을 만들고 있다고 가정
      • 도구 모음의 버튼과 다양한 대화 상자들의 일반 버튼들에 사용할 수 있는 Button 클래스를 생성

  • 버튼의 모양은 비슷해 보이지만 각각 다른 기능을 수행해야 함
  • 요청을 처리하기 위한 함수를 자식 클래스로 상속하고 오버라이딩하는 구조로 구성해야 한다면 기능별로 상속을 다 해주어야 함

  • 이렇게 되면 Button 클래스를 수정할 때마다 자식 클래스가 영향을 받음
    • 로직 수정시 관련 모든 클래스 수정해주어야 함
  • 동일한 기능을 하는 로직이 버튼이 아닌 다른 형태로 표현될 때 해당 기능을 copy & paste 해야 함

  • 명령 패턴의 아이디어
    • 인터페이스 객체들이 요청을 직접 보내는 것이 아닌 Command를 경유해서 보내자

버튼별로 달라지는 비즈니스 동작을 Command 객체로 캡슐화해서, 호출하는 쪽에서는 버튼 타입만으로 적절한 Command를 골라 실행하도록 만듬

  • 수정된 구조
    • 더 이상 다양한 클릭 행동들을 구현하기 위한 버튼의 여러 자식 클래스는 필요하지 않음
    • 커맨드는 사용자 인터페이스와 비즈니스 로직 레이어 간의 결합도를 줄이는 중간 레이어의 역할을 함

Command Pattern 구조

  • Invoker: 버튼 같은 애
  • Command: 버튼이 실행할 '동작'의 인터페이스
  • ConcreteCommand1/2: 실제로 "복사하기, 붙여넣기" 같은 구체 동작
  • Receiver: 에디터, 서비스 같은 진짜 비즈니스 로직 객체
  • Client: 이 관계들을 처음에 연결해주는 설정 코드

Receiver 코드를 수정하면 Invoker도 수정이 필요함
Command Pattern 적용 예시 (Commad: Invoker(Button)와 Receiver(Light or Game) 사이의 새로운 계층)

  • 장점
    • 단일 책임 원칙(SRP)개방/폐쇄 원칙(OCP) 준수
      • SRP: Invoker와 receiver로 책임을 분리하고, 기존 클라이언트 코드에 변경 없이 새로운 커맨드 도입 가능
      • OCP: 새로운 기능을 추가할 때, receiver는 수정이 필요하지 않으며, command만 수정하면 됨
    • 클라이언트와 수신자 간의 결합도 감소
      • 클라이언트는 어떤 커맨드가 어떤 객체에서 어떻게 실행되는지 세부사항을 알 필요 없음
    • 명령 기록과 실행 취소/재실행(undo/redo) 기능 추가 가능
      • Invoker 내부에 stack을 이용하여 command history를 관리 가능
  • 단점
    • Invoker(Button)와 receiver(Light or Game) 사이 완전히 새로운 레이어(command)를 도입하기 때문에 코드가 더 복잡해짐

Summary

  • 책임 연쇄 패턴(Chain of Responsibility Pattern)
    • 클라이언트의 요청에 대한 세세한 처리를 한 객체가 전부 하는 것(스파게티 코드)이 아닌,
      여러 개의 처리 객체들(handler)로 나누고 이들을 사슬(chain)처럼 연결해 연쇄적으로 처리하는 행동 패턴
  • 명령 패턴(Command Pattern)
    • 요청을 객체의 형태로 캡슐화하여 사용자가 보낸 요청을 나중에 이용할 수 있도록 재사용성을 높인 행동 패턴