커맨드 패턴(Command pattern)은 객체가 특정 기능을 바로 수행하거나 나중에 트리거할 때 필요한 모든 정보를 캡슐화하는 행동 패턴이다. 이렇게 하면, 나중에 순서대로 실행하기 위한 명령 목록을 구성하거나 되돌리기가 가능한 명령을 만드는 등이 가능하다. 커맨드 패턴이 캡슐화하는 정보는 다음과 같다.

  • 메서드명
  • 메서드를 소유하는 객체
  • 메서드 인자(parameter)

 

목적

  • 요청을 객체 속에 캡슐화한다.
  • 클라이언트의 다양한 요청을 매개변수화한다.

 

커맨드 패턴의 UML 클래스 다이어그램

Invoker 클래스는 Command 인터페이스를 가진 객체의 execute() 메서드를 호출한다. 사실 이는 ConcreteCommand 클래스의 객체로, execute() 메서드는 실제 작업을 하는 Receiver 클래스의 객체를 호출한다.

  • Invoker: 명령이 들어 있으며, execute() 메서드를 호출함으로써 ConcreteCommand 객체에게 특정 작업의 수행을 요청한다.
  • Receiver: 요구 사항을 수행하기 위해 어떤 일을 처리해야 하는지 알고 있는 객체다.
  • Command: 연산을 수행할 인터페이스를 정의한다. 모든 커맨드 객체는 이 인터페이스를 구현해야 하며, 모든 명령은 execute() 메서드 호출을 통해 수행된다. 이 메서드에서는 Receiver에 특정 작업을 처리하라는 지시를 전달한다.
  • ConcreteCommand: 이 클래스는 Command 인터페이스를 확장하고, execute() 메서드를 구현함으로써 Receiver에 있는 메서드를 호출하여 요청된 작업을 수행한다.

 

장점

  • 작업을 요청하는 클래스와 실제로 작업을 수행하는 클래스를 분리한다.
  • 기존 코드를 수정하지 않고 새로운 커맨드를 쉽게 추가할 수 있다.
  • 정보 시스템의 일반적인 특성은 트랜잭션(transaction)을 처리해야 한다는 것이다. 트랜잭션은 일련의 과정을 통해 데이터를 변경하는 것인데, 커맨드 패턴은 이런 트랜잭션의 모델링을 가능하게 한다.

 

단점

  • 모든 작업이 독립적인 ConcreteCommand 클래스이므로 구현 및 유지보수해야 하는 클래스가 많다.
  • 단 하나의 커맨드에 대해 클래스가 많아진다.

 

예제 코드

# Python Design Patterns - acorn+PACKT의 예제 코드
from abc import ABCMeta, abstractmethod
import os

history = []


class Command(metaclass=ABCMeta):
    @abstractmethod
    def execute(self):
        """커맨드를 실행하기 위한 메서드"""
        pass

    @abstractmethod
    def undo(self):
        """커맨드 실행을 취소하기 위한 메서드"""
        pass


class LsCommand(Command):
    """유닉스 명령어인 ls를 흉내내는 실제 커맨드"""
    def __init__(self, receiver):
        self.receiver = receiver

    def execute(self):
        """리시버로의 호출을 델리게이트하는 커맨드"""
        self.receiver.show_current_dir()

    def undo(self):
        """ls 커맨드는 취소할 수 없음"""
        pass


class LsReceiver:
    def show_current_dir(self):
        """리시버는 어떻게 커맨드를 실행해야 하는지 알고 있음"""
        cur_dir = './'

        filenames = []
        for filename in os.listdir(cur_dir):
            if os.path.isfile(os.path.join(cur_dir, filename)):
                filenames.append(filename)
        print("디렉토리 내용: ", " ".join(filenames))


class TouchCommand(Command):
    """유닉스 명령어 touch를 흉내내는 실제 커맨드"""
    def __init__(self, receiver):
        self.receiver = receiver

    def execute(self):
        self.receiver.create_file()

    def undo(self):
        self.receiver.delete_file()


class TouchReceiver:
    def __init__(self, filename):
        self.filename = filename

    def create_file(self):
        with open(self.filename, 'a'):
            os.utime(self.filename, None)

    def delete_file(self):
        os.remove(self.filename)


class RmCommand(Command):
    """유닉스 명령어 rm을 흉내내는 실제 커맨드"""
    def __init__(self, receiver):
        self.receiver = receiver

    def execute(self):
        self.receiver.delete_file()

    def undo(self):
        self.receiver.undo()


class RmReceiver:
    def __init__(self, filename):
        self.filename = filename
        self.backup_name = None

    def delete_file(self):
        """백업을 만드는 식으로 파일을 삭제하고 실행 취소 메서드에 저장함"""
        self.backup_name = '.' + self.filename
        os.rename(self.filename, self.backup_name)

    def undo(self):
        """삭제한 파일을 되살림"""
        original_name = self.backup_name[1:]
        os.rename(self.backup_name, original_name)
        self.backup_name = None


class Invoker:
    def __init__(self, create_file_commands, delete_file_commands):
        self.create_file_commands = create_file_commands
        self.delete_file_commands = delete_file_commands
        self.history = []

    def create_file(self):
        print("파일을 생성 중...")
        for command in self.create_file_commands:
            command.execute()
            self.history.append(command)
        print("파일이 생성됨")

    def delete_file(self):
        print("파일을 삭제 중...")
        for command in self.delete_file_commands:
            command.execute()
            self.history.append(command)
        print("파일이 삭제됨")

    def undo_all(self):
        print("모두 실행 취소 중...")
        for command in reversed(self.history):
            command.undo()
        print("실행 취소가 모두 끝남")


if __name__ == '__main__':
    ls_receiver = LsReceiver()
    ls_command = LsCommand(ls_receiver)

    touch_receiver = TouchReceiver('test_file')
    touch_command = TouchCommand(touch_receiver)

    rm_receiver = RmReceiver('test_file')
    rm_command = RmCommand(rm_receiver)

    create_file_commands = [ls_command, touch_command, ls_command]
    delete_file_commands = [ls_command, rm_command, ls_command]

    invoker = Invoker(create_file_commands, delete_file_commands)

    invoker.create_file()
    invoker.delete_file()
    invoker.undo_all()

'프로그래밍 관련 > 객체 지향 설계' 카테고리의 다른 글

디자인 패턴. 싱글턴 패턴(Singleton pattern)  (0) 2022.05.08
정리. SOLID 원칙  (0) 2022.01.21
메모. Iterator pattern  (0) 2019.11.22
메모. Proxy pattern  (0) 2019.11.18
메모. Facade pattern  (0) 2019.11.17