[디자인패턴] 행동패턴(8) - Command Pattern (커맨드 패턴)은 명령(요청)을 객체로 캡슐화하여, 명령 실행, 취소(Undo), 재실행(Redo) 등을 유연하게 처리하는 디자인 패턴입니다.
이번 글에서는 Command 패턴의 구조, 예시, 장단점을 살펴본 뒤 Python 예제 코드로 구현 방식까지 함께 보겠습니다.
1. 개요
Command 패턴에서 Command 인터페이스(또는 추상 클래스)는 실행할 메서드를 정의하고, ConcreteCommand는 Receiver에 대한 구체적인 실행 로직을 구현합니다. Invoker는 Command 객체를 실행, 취소(Undo), 재실행(Redo) 등 다양한 방법으로 관리하여 최종적으로 클라이언트의 요청을 올바른 대상에게 전달하고 수행하는 역할을 합니다.
2. 예시
예시로, 스마트 홈에서 전등(Light)을 켜고 끄는 명령을 살펴볼게요. LightOnCommand와 LightOffCommand가 각각 ConcreteCommand가 되고, Light 객체가 Receiver 역할을 담당합니다. 이때, Invoker(RemoteControl)은 명령 객체를 설정(set)한 뒤, 버튼을 누르는 행위를 통해 execute()를 호출합니다.
# Command Pattern in Python (GoF 구조 반영)
class Light:
def on(self):
print("[Python] Light is turned ON.")
def off(self):
print("[Python] Light is turned OFF.")
# Command 인터페이스(또는 추상 클래스) 역할
class Command:
def execute(self):
# 실제 로직은 ConcreteCommand에서 구현
pass
class LightOnCommand(Command):
def __init__(self, light):
self._light = light
def execute(self):
self._light.on() # Receiver가 수행할 동작 (전등 켜기)
class LightOffCommand(Command):
def __init__(self, light):
self._light = light
def execute(self):
self._light.off() # Receiver가 수행할 동작 (전등 끄기)
# Invoker 역할
class RemoteControl:
def __init__(self):
self._command = None
def set_command(self, command):
# Invoker에 명령 객체를 할당
self._command = command
def press_button(self):
# 버튼을 누르면 명령 객체의 execute()를 호출
if self._command:
self._command.execute()
if __name__ == "__main__":
light = Light()
remote = RemoteControl()
# 두 가지 명령 객체 생성
on_command = LightOnCommand(light)
off_command = LightOffCommand(light)
# '켜기' 명령 설정 후 버튼 누르기
remote.set_command(on_command)
remote.press_button() # 전등 켜기
# '끄기' 명령 설정 후 버튼 누르기
remote.set_command(off_command)
remote.press_button() # 전등 끄기
위 구조에서 Command는 'execute()'만 정의하고 구체 로직은 ConcreteCommand들이 구현합니다. Invoker(RemoteControl)는 명령 객체를 할당받아 press_button()을 통해 execute()를 호출합니다. 전등을 켜고 끄는 상세 로직(Receiver)은 Light가 담당하여, Invoker와 Receiver 사이의 결합도를 낮출 수 있습니다. 특히, Undo/Redo 같은 추가 기능을 만들고 싶다면 Command 객체에 명령 실행 이력이나 역으로 돌리는 로직을 넣어 관리할 수도 있습니다.
3. 장점
- 확장성: 새로운 명령을 추가하기 쉽습니다. 명령마다 ConcreteCommand 클래스를 작성하기만 하면 되므로, 기존 코드를 크게 수정하지 않고도 기능을 확장할 수 있습니다.
- Undo/Redo 기능에 유리 명령 실행 이력을 저장해둔 뒤, 역(Undo)으로 돌려보내는 로직을 ConcreteCommand에서 구현하면 재실행(Redo)까지 간편하게 지원할 수 있습니다.
4. 단점
- 클래스 수 증가: 명령마다 별도의 ConcreteCommand 클래스를 작성해야 하므로, 많은 명령이 필요한 시스템에서는 코드 양이 많아질 수 있습니다.
- 복잡성 상승: 작은 기능을 구현할 때도 명령 객체, 수신자, 호출자 등 여러 클래스가 관여하므로, 과도하게 사용하면 오히려 구조가 복잡해질 수 있습니다.
5. 결론
Command 패턴은 요청을 객체로 래핑하여, 다양한 기능을 간편하게 확장할 수 있게 해주는 강력한 디자인 패턴입니다. 특히, Undo/Redo 기능이 필요한 상황에서 명령 실행 이력 관리가 쉬워진다는 점이 큰 이점입니다. 다만, 구현할 명령이 많아질수록 ConcreteCommand가 급격히 늘어날 수 있으므로, 패턴 적용 시 코드 구조와 클래스 수를 제어할 수 있는 전략이 필요합니다. 적절히 활용한다면, 복잡한 기능도 확장성 좋고 깔끔하게 관리할 수 있게 됩니다.