Command Pattern

Understanding the Command Pattern: A design pattern that encapsulates requests as objects for flexible and maintainable code

September 13, 2024


커맨드 패턴은 소프트웨어 디자인 패턴 중 하나로, 요청을 객체의 형태로 캡슐화하여 사용자가 보낸 요청을 나중에 이용할 수 있도록 매개변수화하고, 큐에 넣거나 로그로 기록하거나 작업 취소 기능을 지원할 수 있게 해줍니다.

커맨드 패턴의 주요 구성 요소

  • Command: 실행될 기능에 대한 인터페이스
  • ConcreteCommand: Command 구현하는 클래스
  • Invoker: 기능의 실행을 요청하는 호출자 클래스
  • Receiver: ConcreteCommand에서 실제로 기능을 실행하는 수신자 클래스

제공된 코틀린 코드를 통해 커맨드 패턴의 구현을 살펴보겠습니다.

  1. Command 추상 클래스
abstract class Command {
    abstract fun execute(): String
    abstract fun undo(): String
}

이 추상 클래스는 모든 커맨드가 구현해야 할 기본 구조를 정의합니다. execute() 메서드는 커맨드를 실행하고, undo() 메서드는 실행을 취소합니다.

  1. Receiver 클래스
class Kimchi() {
    fun cookKimchi(): String {
        return "Cook Kimchi"
    }
}

Kimchi 클래스는 실제 작업을 수행하는 Receiver 역할을 합니다.

  1. ConcreteCommand 클래스
class CommandKimchi(private val kimchi: Kimchi) : Command() {
  private val prevCook: String = "None"

  override fun execute(): String {
    return kimchi.cookKimchi()
  }

  override fun undo(): String {
    return prevCook
  }
}

CommandKimchi는 구체적인 커맨드를 구현합니다. 이 클래스는 Kimchi 객체를 받아 실제 작업을 수행합니다.

  1. NoCommand 클래스
class NoCommand : Command() {
  override fun execute(): String {
    return "No command"
  }

  override fun undo(): String {
    return "No command"
  }
}

NoCommand는 Null Object 패턴을 구현한 것으로, 커맨드가 설정되지 않았을 때의 기본 동작을 정의합니다.

  1. Invoker 클래스
class InvokeCommand : Command() {
  private var commend: Command = NoCommand()

  fun setCommend(command: Command) {
    commend = command
  }

  override fun execute(): String {
    return commend.execute()
  }

  override fun undo(): String {
    return commend.undo()
  }
}

InvokeCommand는 Invoker 역할을 합니다. 이 클래스는 실제 커맨드 객체를 가지고 있으며, 클라이언트의 요청에 따라 해당 커맨드를 실행하거나 취소합니다.

  1. 테스트 코드
class CommandPatternTest {
    @Test
    fun testInvokeCommand() {
        val invoker = InvokeCommand()

        // 초기 상태 테스트 (NoCommand)
        assertEquals("No command", invoker.execute())
        assertEquals("No command", invoker.undo())

        // Kimchi 커맨드 설정 및 테스트
        val kimchi = Kimchi()
        val kimchiCommand = CommandKimchi(kimchi)
        invoker.setCommend(kimchiCommand)

        assertEquals("Cook Kimchi", invoker.execute())
        assertEquals("None", invoker.undo())
    }
}

커맨드 패턴의 장점

  • 확장성: 새로운 커맨드를 추가하기 쉽습니다.
  • 단일 책임 원칙: 각 커맨드 클래스는 하나의 작업만 담당합니다.
  • 개방-폐쇄 원칙: 기존 코드를 수정하지 않고 새로운 커맨드를 추가할 수 있습니다.
  • 실행 취소와 재실행: undo() 메서드를 통해 작업 취소 기능을 쉽게 구현할 수 있습니다.
  • 요청의 큐잉과 로깅: 커맨드 객체를 저장하거나 로그로 남길 수 있어 복잡한 연산을 관리하기 용이합니다.

커맨드 패턴은 요청을 객체로 캡슐화하여 클라이언트와 수신자 사이의 의존성을 줄이고, 유연한 시스템 설계를 가능하게 합니다. 이 패턴을 통해 개발자는 복잡한 작업을 단순한 인터페이스로 추상화하고, 시스템의 확장성과 유지보수성을 향상시킬 수 있습니다.