[디자인패턴] Command

Judy·2022년 12월 7일
0

디자인패턴

목록 보기
7/11
post-thumbnail

Command

Behavioral Pattern

요청에 대해 요청에 대한 모든 정보를 포함하는 독립된 객체로 변경하는 패턴

이 변환으로 요청을 메서드 인수로 전달하고 실행을 지연하거나 대기열에 추가하고, 실행을 취소하는 작업을 지원할 수 있습니다.

문제

여러 다양한 버튼을 만들고 싶을 때 Button 클래스를 만들었다고 해보자.

버튼들 마다 각각 다른 기능을 수행해야 하는데 각 버튼들의 클릭 핸들러 코드를 어디에 두어야 할까요? 간단하게 버튼이 사용되는 위치에서 수많은 자식 클래스를 둘 수 있습니다. 이러한 자식 클래스에 버튼 클릭 시 실행될 코드가 들어 있습니다.

이런 방식으로 진행하면 엄청난 수의 자식 클래스가 생기게 됩니다. 또한 Button 클래스를 수정할 때마다 자식 코드에도 영향이 갈 수 있습니다. 즉 GUI 코드가 비즈니스 로직의 불안정한 코드에 의존하게 되었습니다.

'복사 버튼(Copy Button)'을 여러 곳에서 사용하게 되면 해당 작업에 코드를 여러 클래스에 복제하거나 버튼에 의존하는 메뉴를 만들게 됩니다.

해결

올바른 소프트웨어 디자인은 관심사 분리의 원칙을 기반으로 합니다. 예를 들면 GUI 레이어와 비즈니스 로직용 레이어의 분리입니다.

GUI 객체가 비즈니스 논리 객체의 메서드를 호출하고 인수를 전달합니다. 이런 프로세스는 일반적으로 한 객체가 다른 객체에게 요청을 보내는 것이라고 합니다.

Command 패턴은 GUI 객체가 요청을 직접 보내서는 안된다고 합니다. 대신 요청의 세부 정보들(호출 객체, 메서드 이름, 인수 등)을 요청을 작동시키는 단일 메서드를 가진 별도의 Command 클래스로 추출하라고 제안합니다.

커맨드 객체들은 다양한 GUI 객체들과 비즈니스 논리 객체들 간의 링크 역할을 합니다. 이제 GUI 객체는 어떤 비즈니스 논리 객체가 요청을 받을지, 어떻게 요청이 처리될지는 알 필요가 없습니다. Command 객체가 모든 세부사항을 처리하고 GUI 객체는 단지 Command를 작동시키기만 하면 됩니다.

Command들은 같은 인터페이스를 가져야 합니다. 일반적으로 커맨드는 매개 변수를 받지 않는 단일 실행 메서드만을 가집니다. 이러한 인터페이스는 다양한 커맨드들이 Concrete Command와 결합하지 않고 같은 발신자와 사용할 수 있게 해줍니다. 이제 발신자와 연결된 커맨드 객체들을 전환할 수 있으며, 런타임에 발신자의 행동을 변경할 수 있습니다.

커맨드 실행 메서드에는 매개 변수가 없습니다. 그렇다면 어떻게 요청의 세부 정보를 수신자에게 전달할까요? 커맨드를 이러한 데이터로 미리 설정해놓거나, 이 데이터 자체적으로 가져올 수 있도록 해야 합니다.

Button 클래스에 커맨드 객체에 대한 참조를 저장하는 필드를 넣은 후 이 버튼이 클릭될 때 커맨드를 실행하게 하면 됩니다.

이제 모든 작업에 대해 커맨드 클래스를 구현하고 이 클래스들을 특정 버튼과 연결해야 합니다.

결과적으로 커맨드들은 GUI 레이어와 비즈니스 로직 레이어 간의 결합도를 줄이는 편리한 중간 레이어가 됩니다.

실제 예시

멋진 레스토랑에서 친절한 웨이터가 당신의 주문을 종이에 적습니다. 웨이터는 주방에 가서 주문을 요리사에게 전달하고, 요리사는 주문을 읽고 요리를 시작합니다. 요리가 완성되면 웨이터가 요리를 확인하고 당신의 테이블로 가져다 줍니다.

종이에 적힌 주문이 바로 커맨드 역할을 합니다. 주문에는 요리하는 데 필요한 모든 관련 정보가 포함되어 있습니다. 이를 통해 당신이 요리사에게 주문 세부 사항을 직접 전달하는 대신 바로 요리를 시작할 수 있습니다.

구조

  1. Invoker
  • Sender 클래스는 요청 시작을 담당
  • 명령 객체에 대한 참조를 프로퍼티로 가짐 (생성자로 미리 생성된 command를 받음)
  • Receiver로 직접 요청하는 대신 command를 트리거
  1. Command 인터페이스
  • 명령을 실행하는 단일 메서드를 갖게 함
  1. Concrete Command
  • 다양한 종류의 요청을 구현
  • 자체적으로 작업을 수행하는 것이 아닌 여러 객체 중 하나에 호출을 전달
  • 단순한 코드를 위해 여러 클래스를 병합할 수도 있음
  • Receiver에 전달해야 할 매개변수는 프로퍼티로 가질 수 있음
  • 생성자로 프로퍼티를 초기화만 해주면 불변 객체로 만들 수 있음
  1. Receiver
  • 실제 작업을 수행
  • 대부분의 객체가 Receiver 역할을 할 수 있음
  1. Client
  • Concrete Command 객체를 생성
  • Receiver 인스턴스와 요청에 필요한 매개변수를 Command 객체 생성 시 전달
  • Command 객체는 하나 또는 그 이상의 Sender(Invoker)와 연결

적용

  • 특정 작업을 하는 객체를 매개변수화 하고 싶을 때
  • 작업을 연기 시키거나 실행을 예약 또는 원격으로 실행하려는 경우
  • 되돌릴 수 있는 작업을 구현하려는 경우

구현 방법

  1. 실행하는 메서드를 단일로 가지는 Command 인터페이스 선언
  2. Command 인터페이스를 따르는 구체적인 Command 객체 구현

    각 객체에는 실제로 작업을 수행할 객체를 저장할 프로퍼티가 있어야 함
    이러한 값들은 Command 객체를 생성할 때 초기화

  3. Sender 역할을 할 객체 구현

    Command를 저장할 프로퍼티 구현
    Command 인터페이스로 통신
    일반적으로 자체적으로 Command 객체를 생성하지 않고 클라이언트 코드에서 가져옴

  4. Receiver에게 직접 요청을 보내는 대신 Command를 실행하도록 Sender를 변경
  5. 클라이언트는 다음의 순서로 객체를 초기화

    1) Receiver 생성
    2) Command 객체를 생성하고 칠요한 경우 Receiver와 연결
    3) Sender를 생성하고 특정 Command 객체와 연결


장단점

✅ 장점

  • SRP 원칙 준수 - 작업을 수행하는 객체를 작업을 호출하는 객체와 분리
  • OCP 원칙 준수 - 클라이언트 코드를 수정하지 않고 새로운 명령을 추가할 수 있음
  • undo, redo를 구현 가능
  • 작업을 지연 실행하는 기능 구현 가능
  • 간단한 명령들을 명령 세트로 모을 수 있음

❎ 단점

  • Receiver와 Sender 사이에 새로운 계층을 도입하는 것으로 코드가 복잡해질 수 있음



Command

profile
iOS Developer

0개의 댓글