[Design pattern] 3-3. 커맨드 패턴 (Command pattern)


Study/Design Pattern  2021. 11. 5. 16:50

안녕하세요. 명월입니다.


이 글은 디자인 패턴의 커맨드 패턴(Command pattern)에 대한 글입니다.


커맨드 패턴(Command pattern)은 살짝 복잡한 패턴입니다만, 간단하게 이야기하면 발동자(invoker)가 수신자(receiver)를 실행하기 위해서 명령자(command)를 가운데 두는 패턴입니다.

보통 커맨드 패턴은 전등의 예로 설명을 많이 합니다만, 스위치(invoker)가 있고 전등(receiver)이 있습니다. 그것을 전원 ON과 OFF의 명령자(command)가 있는 형태입니다.

출처 - https://en.wikipedia.org/wiki/Command_pattern

#pragma once
#include <stdio.h>
#include <iostream>
using namespace std;
// 전등 클래스(reciever)
class Light {
public:
  // 전등 켜질 때 함수
  void on() {
    // 콘솔 출력
    cout << "Power on!" << endl;
  }
  // 전등 꺼질 때 함수
  void off() {
    // 콘솔 출력
    cout << "Power off!" << endl;
  }
};
// 커맨드 인터페이스
class ICommand {
public:
  // 함수 추상화
  virtual void execute() = 0;
};
// 전등 키는 클래스, 커맨드 인터페이스 상속
class TurnOnCommand : public ICommand {
private:
  // 전등 인스턴스 맴버 변수
  Light* light;
public:
  // 생성자
  TurnOnCommand(Light* light) {
    // 맴버 변수 설정
    this->light = light;
  }
  // 실행 함수 재정의
  void execute() {
    // 전등 켜는 함수 실행
    this->light->on();
  }
};
// 전등 끄는 클래스, 커맨드 인터페이스 상속
class TurnOffCommand : public ICommand {
private:
  // 전등 인스턴스 맴버 변수
  Light* light;
public:
  // 생성자
  TurnOffCommand(Light* light) {
    // 맴버 변수 설정
    this->light = light;
  }
  // 실행 함수 재정의
  void execute() {
    // 전등 끄는 함수 실행
    this->light->off();
  }
};
// 스위치(invoker)
class Switch {
private:
  // 커맨드 맴버 변수
  ICommand* turnOnCmd;
  ICommand* turnOffCmd;
public:
  // 생성자
  Switch(ICommand* turnOnCmd, ICommand* turnOffCmd) {
    // 맴버 변수 설정
    this->turnOnCmd = turnOnCmd;
    this->turnOffCmd = turnOffCmd;
  }
  // 실행 함수
  void run() {
    // 스위치 켜는 커맨드 실행
    this->turnOnCmd->execute();
    // 콘솔 출력
    cout << "It turns off after 5 minutes." << endl;
    cout << "It turns off after 4 minutes." << endl;
    cout << "It turns off after 3 minutes." << endl;
    cout << "It turns off after 2 minutes." << endl;
    cout << "It turns off after 1 minutes." << endl;
    // 스위치 끄는 커맨드 실행
    this->turnOffCmd->execute();
  }
};
// 실행 함수
int main() {
  // 전등 인스턴스 생성
  Light light;
  // 커맨드 인스턴스 생성
  TurnOnCommand turnOnCmd(&light);
  TurnOffCommand turnOffCmd(&light);
  // 스위치 인스턴스 생성(switch는 키워드라 변수 선언이 안되네요...;;;)
  Switch button(&turnOnCmd, &turnOffCmd);
  // 실행
  button.run();

  return 0;
}

위 예를 보면 쉽게 이해가 됩니다. 커맨드 패턴은 발동자(invoker)의 함수를 클래스 별로 나눈 것입니다.

함수는 인스턴스를 구현할 수 없기 때문에 함수 별로 인스턴스를 만든 형태가 커맨드 패턴(Command pattern)입니다.

import java.util.ArrayList;
import java.util.List;
// Node 클래스
class Node {
  // processA 함수
  void processA() {
    // 콘솔 출력
    System.out.println("execute ProcessA");
  }
  // processB 함수
  void processB() {
    // 콘솔 출력
    System.out.println("execute ProcessB");
  }
}
// 커맨드 추상 클래스
abstract class ACommand {
  // Node 클래스 맴버 변수
  private Node node;
  // 생성자
  public ACommand(Node node) {
    // 맴버 변수 설정
    this.node = node;
  }
  // 맴버 변수 취득 함수
  protected Node getNode() {
    return this.node;
  }
  // 실행 함수 추상화
  public abstract void execute();
}
// ProcessACommand 클래스, 추상 클래스 ACommand 상속
class ProcessACommand extends ACommand {
  // 생성자
  public ProcessACommand(Node node) {
    super(node);
  }
  // 실행
  public void execute() {
    // Node 인스턴스를 취득해서 processA함수 실행
    getNode().processA();
  }
}
// ProcessBCommand 클래스, 추상 클래스 ACommand 상속
class ProcessBCommand extends ACommand {
  // 생성자
  public ProcessBCommand(Node node) {
    super(node);
  }
  // 실행
  public void execute() {
    // Node 인스턴스를 취득해서 processB함수 실행
    getNode().processB();
  }
}
// Procedure 클래스
class Procedure {
  // 리스트 맴버 변수
  private List<ACommand> commands = new ArrayList<>();
  // 커맨드 추가
  public void addCommand(ACommand command) {
    // 리스트에 커맨드 추가
    commands.add(command);
  }
  // 실행
  public void run() {
    // 리스트의 순서대로
    for (var cmd : commands) {
      // 실행
      cmd.execute();
    }
  }
}

public class Program {
  // 실행 함수
  public static void main(String[] args) {
    // Node 인스턴스 생성
    var node = new Node();
    // Procedure 인스턴스 생성
    var procedure = new Procedure();
    // ProcessACommand 인스턴스 추가
    procedure.addCommand(new ProcessACommand(node));
    // ProcessBCommand 인스턴스 추가
    procedure.addCommand(new ProcessBCommand(node));
    // ProcessACommand 인스턴스 추가
    procedure.addCommand(new ProcessACommand(node));
    // 실행
    procedure.run();
  }
}

함수를 클래스의 인스턴스로 만들 수 있으면 위처럼 list 등으로 명령 순서를 설정할 수도 있습니다.

using System;
using System.Collections.Generic;
// 자동차 클래스
class Car
{
  // 좌회전 함수
  public void LeftTurn()
  {
    // 콘솔 출력
    Console.WriteLine("LeftTurn!");
  }
  // 우회전 함수
  public void RightTurn()
  {
    // 콘솔 출력
    Console.WriteLine("RightTurn!");
  }
  // 악셀 함수
  public void Accelerator()
  {
    // 콘솔 출력
    Console.WriteLine("Accelerator!");
  }
  // 브레이크 함수
  public void Break()
  {
    // 콘솔 출력
    Console.WriteLine("Break!");
  }
}
// 커맨드 추상 클래스
abstract class ACommand
{
  // 맴버 변수
  private Car car = new Car();
  // 생성자
  public ACommand(Car car)
  {
    // 맴버 변수 설정
    this.car = car;
  }
  // 실행
  public void Execute()
  {
    // 추상 함수 실행
    Run(car);
  }
  // 추상 함수
  protected abstract void Run(Car car);
}
// 좌회전 커맨드 클래스
class LeftTurnCommand : ACommand
{
  // 생성자
  public LeftTurnCommand(Car car) : base(car) { }
  // 추상 함수 재정의
  protected override void Run(Car car)
  {
    // 좌회전 함수 실행
    car.LeftTurn();
  }
}
// 우회전 커맨드 클래스
class RightTurnCommand : ACommand
{
  // 생성자
  public RightTurnCommand(Car car) : base(car) { }
  // 추상 함수 재정의
  protected override void Run(Car car)
  {
    // 우회전 함수 실행
    car.RightTurn();
  }
}
// 악셀 커맨드 클래스
class AcceleratorCommand : ACommand
{
  // 생성자
  public AcceleratorCommand(Car car) : base(car) { }
  // 추상 함수 재정의
  protected override void Run(Car car)
  {
    // 악셀 함수 실행
    car.Accelerator();
  }
}
// 브레이크 커맨드 클래스
class BreakCommand : ACommand
{
  // 생성자
  public BreakCommand(Car car) : base(car) { }
  // 추상 함수 재정의
  protected override void Run(Car car)
  {
    // 브레이크 함수 실행
    car.Break();
  }
}
// 운전 클래스
class Driving
{
  // 커맨드 리스트
  private List<ACommand> cmds = new List<ACommand>();
  // 브레이크 커맨드 맴버 변수
  private ACommand breakCmd;
  // 생성자
  public Driving(ACommand breakCmd)
  {
    // 브레이크 커맨드 맴버 변수 설정
    this.breakCmd = breakCmd;
  }
  // 커맨드 추가 함수
  public void AddCommand(ACommand cmd)
  {
    // 커맨드 추가
    cmds.Add(cmd);
  }
  // 출발
  public void Start()
  {
    // 커맨드 리스트에서 커맨드 취득
    foreach (var cmd in this.cmds)
    {
      // 커맨드 실행
      cmd.Execute();
      // 브레이크 커맨드 실행
      this.breakCmd.Execute();
    }
  }
}
// 실행 클래스
class Program
{
  // 실행 함수
  static void Main(string[] args)
  {
    // 자동차 인스턴스 생성
    Car car = new Car();
    // 운전 인스턴스 생성
    Driving driving = new Driving(new BreakCommand(car));
    // 명령 추가(코맨드 인스턴스 추가)
    driving.AddCommand(new AcceleratorCommand(car));
    driving.AddCommand(new LeftTurnCommand(car));
    driving.AddCommand(new RightTurnCommand(car));
    // 출발
    driving.Start();
    // 아무 키나 누르면 종료
    Console.WriteLine("Press Any key...");
    Console.ReadLine();
  }
}

커맨드 패턴은 중요한 점이 발동자(invoker)의 함수를 클래스 별로 나눈 것입니다. 즉, 여러가지 함수를 복합적으로 커맨드 패턴으로 만들 수 있습니다.

여기서의 예제는 발동자(invoker)의 클래스를 하나만 생성해서 커맨드 패턴을 만들었지만, 여러 개의 발동자 클래스를 커맨드 패턴 별로 나누고 전략 패턴과 같이 묶어서 사용할 수 도 있습니다.


여기까지 디자인 패턴의 커맨드 패턴(Command pattern)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.