커맨드 패턴
Coddy Python 여정의 Object Oriented Programming 섹션에 포함된 레슨 — 64개 중 49번째.
커맨드 패턴(Command Pattern)은 요청을 객체로 캡슐화하여, 작업을 큐에 저장하거나 요청을 로그로 기록하고, 실행 취소(undo) 기능을 지원할 수 있게 합니다. 이는 작업을 호출하는 객체와 작업을 수행하는 객체를 분리합니다.
다음은 간단한 명령 클래스들입니다:
class Command:
def execute(self):
pass
class LightOnCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_on()
class LightOffCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_off()각 명령은 수신자 객체에 대한 특정 작업을 캡슐화합니다.
실제 작업을 수행하는 수신자(receiver)를 생성합니다:
class Light:
def turn_on(self):
print("Light is on")
def turn_off(self):
print("Light is off")명령을 실행하는 인보커를 생성합니다:
class RemoteControl:
def __init__(self):
self.command = None
def set_command(self, command):
self.command = command
def press_button(self):
self.command.execute()커맨드 패턴을 사용하세요:
# 수신자(receiver) 생성
light = Light()
# 커맨드 생성
light_on = LightOnCommand(light)
light_off = LightOffCommand(light)
# 호출자(invoker) 생성
remote = RemoteControl()
# 다양한 커맨드 실행
remote.set_command(light_on)
remote.press_button()
remote.set_command(light_off)
remote.press_button()실행 취소 작업에 대한 지원을 추가합니다:
class UndoableCommand(Command):
def undo(self):
pass
class LightOnCommand(UndoableCommand):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_on()
def undo(self):
self.light.turn_off()
class SmartRemote:
def __init__(self):
self.last_command = None
def execute_command(self, command):
command.execute()
self.last_command = command
def undo(self):
if self.last_command:
self.last_command.undo()
smart_remote = SmartRemote()
smart_remote.execute_command(LightOnCommand(light))
smart_remote.undo() # 조명을 끕니다출력:
Light is on
Light is off
Light is on
Light is off핵심 포인트: 커맨드 패턴(Command Pattern)은 요청을 저장, 전달 및 나중에 실행할 수 있는 객체로 변환합니다. 호출자(invoker)는 작업을 수행하는 방법을 알 필요가 없습니다. 단지 커맨드 객체의 execute()를 호출할 뿐입니다. 이를 통해 실행 취소/다시 실행(undo/redo), 작업 큐잉(queuing operations), 커맨드 로깅(logging commands)과 같은 기능을 구현할 수 있습니다.
챌린지
쉬움이 챌린지에서는 command.py에 커맨드 패턴의 필수 구성 요소인 기초 Command 베이스 클래스를 구현하게 됩니다. 이 연습은 특히 캡슐화에 초점을 맞춥니다: 데이터를 비공개로 저장하고 프로퍼티를 통해 안전하게 노출하는 방식입니다.
TODO 주석에 따라 command.py만 수정하세요. TODO 주석은 다음을 수행하도록 안내합니다:
- 커맨드 이름을 비공개 속성(
_name)으로 저장하기 - 읽기 전용 프로퍼티(
name)를 통해 노출하기 - 커맨드 이름을 출력하는
display_info()메서드 구현하기
참고: 이 챌린지는 베이스 Command 클래스만 다룹니다. Invoker, Receiver 및 실행 취소(undo) 기능은 이후 레슨에서 소개됩니다. 이 단계를 완료하면 전체 패턴의 기반이 되는 캡슐화 기초를 다질 수 있습니다.
구현한 내용은 다음 사항을 검증하는 driver.py에 의해 테스트됩니다:
- 기본 기능 및 출력 형식
- 엣지 케이스 (빈 입력, 특수 문자, 긴 이름)
- 읽기 전용 프로퍼티 보호 (
obj.name = ...시도를 하면AttributeError가 발생해야 함) - 비공개
_name속성의 존재 여부
치트 시트
커맨드 패턴(Command Pattern)은 요청을 객체로 캡슐화하여, 작업을 큐에 저장하거나 로그를 기록하고 실행 취소(undo) 기능을 지원할 수 있게 합니다.
기본적인 커맨드 구조:
class Command:
def execute(self):
pass
class LightOnCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_on()
class LightOffCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_off()실제 작업을 수행하는 수신자(receiver)를 생성합니다:
class Light:
def turn_on(self):
print("Light is on")
def turn_off(self):
print("Light is off")커맨드를 실행하는 호출자(invoker)를 생성합니다:
class RemoteControl:
def __init__(self):
self.command = None
def set_command(self, command):
self.command = command
def press_button(self):
self.command.execute()사용 예시:
# 수신자 생성
light = Light()
# 커맨드 생성
light_on = LightOnCommand(light)
light_off = LightOffCommand(light)
# 호출자 생성
remote = RemoteControl()
# 다양한 커맨드 실행
remote.set_command(light_on)
remote.press_button()
remote.set_command(light_off)
remote.press_button()실행 취소(undo) 기능 추가:
class UndoableCommand(Command):
def undo(self):
pass
class LightOnCommand(UndoableCommand):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_on()
def undo(self):
self.light.turn_off()
class SmartRemote:
def __init__(self):
self.last_command = None
def execute_command(self, command):
command.execute()
self.last_command = command
def undo(self):
if self.last_command:
self.last_command.undo()직접 해보기
# command.py에서 Command 클래스를 가져옵니다.
from command import Command
# 종합 테스트 케이스 처리기
test_case = input()
if test_case == "basic_test":
obj = Command("Test Name")
obj.display_info()
elif test_case == "validation_test":
obj = Command("Validation Test")
print(f"Name: {obj.name}")
elif test_case == "empty_name_test":
obj = Command("")
obj.display_info()
print(f"Empty name handled: {'Yes' if obj.name == '' else 'No'}")
elif test_case == "property_access_test":
obj = Command("Property Test")
original_name = obj.name
try:
# name은 읽기 전용 속성이므로 실패해야 합니다.
obj.name = "Modified Name"
print("Property protection failed")
except AttributeError:
print("Property protected successfully")
print(f"Name unchanged: {obj.name == original_name}")
elif test_case == "multiple_commands_test":
commands = [
Command("First Command"),
Command("Second Command"),
Command("Third Command")
]
for cmd in commands:
cmd.display_info()
elif test_case == "attribute_test":
obj = Command("Attribute Test")
has_private_name = hasattr(obj, "_name")
print(f"Has _name attribute: {has_private_name}")
elif test_case == "special_chars_test":
obj = Command("!@#$%^&*()_+{}[]|\\:;\"'<>,.?/")
obj.display_info()
elif test_case == "long_name_test":
long_name = "A" * 100
obj = Command(long_name)
obj.display_info()
print(f"Name length: {len(obj.name)}")이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.