본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] 명령 패턴(Command Pattern)을 함수 객체로 단순화하는 3가지 방법과 7가지 실무 예제

by Papa Martino V 2026. 4. 1.
728x90

Command Pattern
Command Pattern

 

 

소프트웨어 설계에서 명령 패턴(Command Pattern)은 요청을 객체의 형태로 캡슐화하여 사용자가 보낸 요청을 나중에 이용할 수 있도록 매개변수화하거나, 큐에 저장하거나, 로깅하며, 취소(Undo) 기능을 지원하게 하는 행동 디자인 패턴입니다. 하지만 전통적인 Java나 C++ 방식의 클래스 기반 구현은 파이썬의 동적 특성과 일급 객체(First-class Object)로서의 함수 기능을 활용할 때 다소 비효율적이고 비대해질 수 있습니다. 본 포스팅에서는 파이썬의 강력한 기능인 Callable 객체와 lambda, partial을 활용하여 복잡한 클래스 구조를 지우고, 코드를 50% 이상 줄이면서도 유지보수성은 높이는 명령 패턴의 파이썬식 최적화 해결 방안을 심도 있게 다룹니다.


1. 클래스 기반 명령 패턴 vs 함수형 명령 패턴의 차이

전통적인 방식은 Command 인터페이스를 상속받는 수많은 서브 클래스를 생성해야 합니다. 반면, 파이썬에서는 함수 자체가 객체이므로 별도의 클래스 선언 없이도 명령을 전달할 수 있습니다. 아래 표를 통해 두 방식의 근본적인 차이를 비교해 보겠습니다.

비교 항목 클래스 기반 명령 패턴 (전통적) 함수 객체 기반 명령 패턴 (파이썬식)
구현 복잡도 높음 (인터페이스 및 다수 클래스 필요) 낮음 (함수 또는 Callable 활용)
코드 양 많음 (Boilerplate 코드 발생) 매우 적음 (간결한 문법)
유연성 정적 (컴파일 시 구조 결정) 동적 (런타임에 함수 바인딩 용이)
상태 저장 인스턴스 변수에 저장 클로저(Closure) 또는 partial에 저장
실행 속도 객체 생성 오버헤드 미세 발생 직접 호출로 오버헤드 최소화

2. 함수 객체로 단순화하는 핵심 기법

파이썬에서 명령 패턴을 단순화할 때 가장 많이 사용되는 3가지 기법은 다음과 같습니다.

  • First-class Functions: 함수를 변수에 할당하고 인자로 전달합니다.
  • functools.partial: 함수의 인자 일부를 미리 고정하여 새로운 호출 가능 객체를 만듭니다.
  • __call__ 매직 메서드: 클래스 인스턴스를 함수처럼 호출 가능하게 만들어 상태와 로직을 동시에 잡습니다.

3. 실무 적용을 위한 7가지 Sample Examples

개발자가 실무 현업에서 바로 복사하여 적용할 수 있는 수준의 고도화된 예제들입니다.

Example 1: 원격 제어 시스템 (Basic Function Mapping)

전통적인 스위치 인터페이스를 사전(Dictionary)과 함수 매핑으로 해결한 사례입니다.


def turn_on():
    return "Light is ON"

def turn_off():
    return "Light is OFF"

# 명령을 함수 객체로 매핑
commands = {
    "ON": turn_on,
    "OFF": turn_off
}

def execute_command(cmd_key):
    # 클래스 없이 즉시 호출
    action = commands.get(cmd_key)
    if action:
        print(action())

execute_command("ON")

Example 2: Undo 기능을 포함한 트랜잭션 매니저

함수 객체 쌍을 튜플로 관리하여 실행과 취소를 동시에 처리하는 구조입니다.


class TransactionManager:
    def __init__(self):
        self.history = []

    def execute(self, command, undo_command):
        command()
        self.history.append(undo_command)

    def undo(self):
        if self.history:
            undo_action = self.history.pop()
            undo_action()

# 실무 적용
def update_db(): print("DB Updated")
def rollback_db(): print("DB Rolled Back")

manager = TransactionManager()
manager.execute(update_db, rollback_db)
manager.undo()

Example 3: GUI 버튼 이벤트 바인딩 (functools.partial 활용)

인자가 포함된 명령을 전달할 때 lambda보다 안전한 partial을 사용하는 방법입니다.


from functools import partial

def send_message(protocol, text):
    print(f"[{protocol}] Sending: {text}")

# 특정 프로토콜이 고정된 명령 객체 생성
send_http = partial(send_message, "HTTP")
send_ftp = partial(send_message, "FTP")

# 이벤트 리스너에서 호출
def click_event(command_obj):
    command_obj("Hello World")

click_event(send_http)

Example 4: 비동기 작업 큐 (Async Command Task)

비동기 함수를 명령 객체로 큐에 쌓아 순차적으로 처리하는 방식입니다.


import asyncio

async def async_task(name, duration):
    await asyncio.sleep(duration)
    print(f"Task {name} completed")

async def worker(queue):
    while not queue.empty():
        cmd = await queue.get()
        await cmd()
        queue.task_done()

async def main():
    queue = asyncio.Queue()
    # 명령들을 큐에 삽입 (함수와 인자를 래핑)
    queue.put_nowait(lambda: async_task("A", 1))
    queue.put_nowait(lambda: async_task("B", 2))
    
    await worker(queue)

# asyncio.run(main()) # 실무 환경에서 실행

Example 5: 동적 매크로 레코더 (Macro Recording)

사용자의 행위를 함수 리스트로 수집하여 한 번에 재생하는 매크로 기능입니다.


class MacroRecorder:
    def __init__(self):
        self.commands = []

    def add_command(self, func, *args, **kwargs):
        self.commands.append(partial(func, *args, **kwargs))

    def run_all(self):
        for cmd in self.commands:
            cmd()

def move(x, y): print(f"Move to {x}, {y}")
def click(): print("Mouse Clicked")

recorder = MacroRecorder()
recorder.add_command(move, 10, 20)
recorder.add_command(click)
recorder.run_all()

Example 6: 유효성 검사 파이프라인 (Chain of Commands)

데이터가 들어왔을 때 여러 검증 함수를 순차적으로 통과시키는 명령 체인입니다.


def check_length(data):
    return len(data) > 5

def check_type(data):
    return isinstance(data, str)

validators = [check_type, check_length]

def validate_pipeline(data):
    # 모든 명령(함수)이 True를 반환해야 함
    return all(v(data) for v in validators)

print(validate_pipeline("PythonCommand")) # True

Example 7: 상태 보존형 Callable 클래스 (Stateful Command)

단순 함수로는 부족할 때 __call__을 활용해 상태를 유지하는 명령 객체입니다.


class CounterCommand:
    def __init__(self):
        self.count = 0

    def __call__(self, increment=1):
        self.count += increment
        print(f"Current Count: {self.count}")

# 인스턴스가 함수처럼 행동함
increment_cmd = CounterCommand()
increment_cmd()   # 1
increment_cmd(5)  # 6

4. 결론 및 설계 권고안

파이썬에서 명령 패턴을 구현할 때는 굳이 상속 계층을 만들 필요가 없습니다. "함수는 객체다"라는 철학을 이해하면 코드가 훨씬 간결해집니다. 상태가 필요 없다면 일반 함수나 lambda를, 고정된 인자가 필요하다면 functools.partial을, 복잡한 내부 상태가 필요하다면 __call__을 구현한 클래스를 사용하십시오. 이러한 방식은 가독성을 높일 뿐만 아니라, 유닛 테스트 시 모킹(Mocking)이 용이해져 전체적인 소프트웨어 품질을 향상시킵니다.


5. 내용의 출처 및 참고 문헌

  • Design Patterns: Elements of Reusable Object-Oriented Software (Gang of Four)
  • Fluent Python: Clear, Concise, and Effective Programming by Luciano Ramalho
  • Python Software Foundation Official Documentation (functools module)
  • Refactoring.Guru - Command Pattern in Python
728x90