디자인 패턴 | 명령 패턴

Jihun Kim·2022년 8월 11일
0

디자인 패턴

목록 보기
4/4
post-thumbnail

이 글은 헤드 퍼스트 디자인 패턴을 읽고 정리한 것입니다.


커맨트 패턴이란?

커맨트 패턴은 객체 지향 디자인 패턴을 통해 요청을 하는 객체와 요청을 받고 실행하는 객체를 분리한 패턴으로 요청자(invoker)와 수신자(receiver)가 decoupling 되어 있다.

실행 순서

  1. 클라이언트가 command 객체를 생성한다.
  2. 클라이언트는 호출자(invoker)에 command 객체를 저장하기 위해 setCommand()를 실행한다.
  3. 클라이언트가 호출자에게 명령을 실행하도록 요청한다.
    • 명령이 호출자에게 등록 되면 해당 명령은 실행 후 삭제될 수도 있지만 그대로 남아 있을 수도 있으며 여러 번 사용하는 것 역시 가능하다.

  • Command 객체는 수신자(receiver)에 가해지는 일련의 행동들을 연결(bind) 함으로써 요청을 캡슐화 한다.
  • 그러면 command 객체는 행동과 수신자를 하나의 객체로 포장한 뒤 요청을 실행할 수 있는 하나의 메소드, 예를 들면 execute()만 노출 시킨다.
  • 이후 이 메소드는 수신자에 가해야 하는 특정 행동이 발생하도록 만든다.
  • 외부적으로 볼 때 다른 객체들은 수신자에 어떤 일이 일어났는 지 알 수 없으며 execute() 메소드가 실행 되었다는 것만 안다.

예시
리모컨으로 불을 켜는 것에 대한 책의 예시를 파이썬으로 작성 했다.

from abc import *


# The example from the Head First Design Pattern
"""
Receiver: Some appliances 
"""


class Light:
    def on(self):
        print("on")

    def off(self):
        print("off")


"""
Command: on, off
"""


class Command(metaclass=ABCMeta):
    @abstractmethod
    def execute(self):
        raise NotImplementedError()


class LightOnCommand(Command):
    def __init__(self, light: Light):
        self.light = light

    def execute(self) -> None:
        self.light.on()


"""
Invoker: RemoteControl
"""


class RemoteControl:
    def __init__(self, command: Command):
        self.slot = command  # A kind of setter in Java

    def button_pressed(self) -> None:
        self.slot.execute()


# Usage
class RemoteControlTest:
    @classmethod
    def run(cls):
        light = Light()
        light_on = LightOnCommand(light=light)

        remote = RemoteControl(command=light_on)
        remote.button_pressed()  # on

커맨드 확장하기

Macro 커맨드

  • Macro 커맨드는 여러 개의 커맨드가 요청될 수 있도록 하는 방법으로, 커맨드를 리스트에 넣어 execute() 실행시 리스트에 들어 있는 커맨드들이 하나씩 실행되도록 만들면 된다.

예시
책의 예시를 파이썬으로 바꾸어 작성해 보았다.

# The example from the Head First Design Pattern
"""
Receiver: Some appliances
"""
from abc import ABCMeta, abstractmethod
from typing import List


class Light:
    def on(self, type: str):
        on_by_types = {
            "living_room": "living_room_light_on",
            "bath_room": "living_room_light_on",
        }
        print(on_by_types.get(type))

    def off(self, type: str):
        on_by_types = {
            "living_room": "living_room_light_off",
            "bath_room": "living_room_light_off",
        }
        print(on_by_types.get(type))


class Stereo:
    def on(self):
        print("on")

    def off(self):
        print("off")

    def set_cd(self):
        print("CD in")


"""
Command: on, off
"""


class Command(metaclass=ABCMeta):
    @abstractmethod
    def execute(self):
        raise NotImplementedError()


class LightOnCommand(Command):
    """Light"""

    def __init__(self, light: Light, type: str):
        self.light = light
        self.type = type

    def execute(self) -> None:
        self.light.on(self.type)


class LightOffCommand(Command):
    """Light"""

    def __init__(self, light: Light, type: str):
        self.light = light
        self.type = type

    def execute(self) -> None:
        self.light.off(self.type)


class StereoOnWithCDCommand(Command):
    """Stereo"""

    def __init__(self, stereo: Stereo):
        self.stereo = stereo

    def execute(self) -> None:
        self.stereo.on()
        self.stereo.set_cd()


"""
Invoker: RemoteControl
"""


class RemoteControl:
    def __init__(self, on_commands: List[Command], off_commands: List[Command]):
        self.on_commands = on_commands  # A kind of setter in Java
        self.off_commands = off_commands  # A kind of setter in Java

    def on_button_pressed(self, slot: int) -> None:
        self.on_commands[slot].execute()

    def off_button_pressed(self, slot: int) -> None:
        self.off_commands[slot].execute()


# Usage
class RemoteLoader:
    @classmethod
    def run(cls):
        light = Light()
        stereo = Stereo()

        on_commands = [
            LightOnCommand(light=light, type="living_room"),
            StereoOnWithCDCommand(stereo=stereo),
        ]
        off_commands = [
            LightOffCommand(light=light, type="living_room"),
            StereoOnWithCDCommand(stereo=stereo),
        ]

        remote = RemoteControl(on_commands=on_commands, off_commands=off_commands)

        length_of_commands = len(on_commands)
        for slot in range(length_of_commands):
            remote.on_button_pressed(slot=slot)

전체 커맨드 취소 기능

  • 커맨드 패턴을 이용하면 전체 커맨드를 취소하는 것도 가능한데 이는 가장 마지막(최근의) 상태를 저장해 요청자의 execute() 메소드가 불리기 이전의 상태로 되돌리면 된다.

특징

  • 실행될 기능이 변경 되어도 호출자(invoker) 클래스를 수정 하지 않아도 된다.
    - 따라서, 변경이 잦거나 다양한 이벤트들이 발생해야 하는 경우 커맨트 패턴을 이용하기 좋다.
  • 호출자 클래스는 execute()만 실행하면 되며, 이 메소드는 각각의 이벤트에 대한 커맨트를 실행한다.
  • 만약 요청을 하는 객체와 요청을 어떻게 실행해야 하는 지 아는 객체를 서로 분리(decouple) 해야 한다면 커맨드 패턴을 쓰면 된다.
  • 요청자는 Command를 통해 파라미터화 될 수 있는 1급 객체이며 런타임에 파라미터화 하는 것도 가능하다.
    - 1급 객체: 변수나 데이터에 할당할 수 있으며 객체에 인자로 넘길 수 있고 객체의 리턴 값으로 반환할 수 있는 객체를 말한다.
profile
쿄쿄

0개의 댓글