Coding Memo

Command Pattern [커맨드 패턴] 본문

etc

Command Pattern [커맨드 패턴]

minttea25 2023. 6. 18. 13:16

Command 패턴은 어떤 객체를 활용할 때 그 객체를 직접 조작하는 것 대신에, 조작을 어떻게 하라고 명령을 보내는 방식을 사용하는 패턴이다.

조작 방식 명령을 보낼 때, Command라는 인터페이스나 추상 클래스를 포함하도록 캡슐화 하여 중간 매개체인 Invoker는 Command에 대한 구체적인 내용은 알 수 없도록 한다.

요청을 매개변수화, 요청 대기 열, 로깅, 실행 취소 등에 활용 될 수 있는 패턴이다.


커맨드 패턴

Command Pattern

Command 요청을 캡슐화 하는 인터페이스나 추상 클래스로 execute를 구현하고 이 요청을 실행할 수신자(receiver)를 가지고 있는 개체
Invoker 작업 수행 명령 수신자에 요청하는 매개체로 클라이언트로부터 받은 실행 명령을 설계된 패턴에 의해 수신자에게 명려을 요청
Receiver Invoker에 의해 요청된 명령을 실질적으로 수행하는 개체
Client 명령 개체를 생성하고 수신기를 가지고 있으며 명령을 Invoker와 연결하고 명령 실행을 트리거

 

Client에서 Receiver로 명령을 직접 전달하지 않고 Invoker를 거치게 함으로써 Invoker에서 명령을 기록, 큐잉 등의 여러가지 설계를 가능하도록하는 것이 커맨드 패턴의 핵심이다. 또한 모든 명령을 단순히 메서드화가 아닌 캡슐화를 통해 클래스를 만드는 것 또한 필요하다.


UML

 

Command Pattern UML

Note: CommandBase를 인터페이스로 할지, 추상 클래스로 할지는 현재 구현하려는 목적에 따라 다르게 하면 된다.


구현 (Example)

 

어떤 플레이어를 두고 플레이어에게 Attack과 Defense를 하도록 명령 수행을 요청하는 코드를 구현 예시로 해보겠다.

 

Command

public abstract class Command
{
    public abstract void Execute();
}

 

Player (Receiver)

더보기
public class Player
{
    public void Attack()
    {
        Console.WriteLine("Player - Attack!");
    }

    public void Defense()
    {
        Console.WriteLine("Player - Defense!");
    }
}

 

 

AttackCommand, DefenseCommand

더보기
public class AttackCommand : Command
{
    Player _player;

    public AttackCommand(Player player)
    {
        _player = player;
    }

    public override void Execute()
    {
        _player.Attack();
    }
}

public class DefenseCommand : Command
{
    Player _player;

    public DefenseCommand(Player player)
    {
        _player = player;
    }

    public override void Execute()
    {
        _player.Defense();
    }
}

 

Recorder (Invoker)

더보기
public class Recorder
{
    int time = 100;
    Dictionary<int, Command> _recordedCommands = new();

    public void ExecuteCommand(Command command)
    {
        command.Execute();

        // record the command
        _recordedCommands.Add(time, command);
        time += 100;
    }

    public void Replay()
    {
        foreach (int t in _recordedCommands.Keys)
        {
            Console.WriteLine($"Time: {t}");
            _recordedCommands[t].Execute();
        }
    }
}

 

Main

class Program
    {
        public static void Main()
        {
            {
                Player player = new();
                Recorder invoker = new();

                AttackCommand attackCommand = new(player);
                DefenseCommand defenseCommand = new(player);

                invoker.ExecuteCommand(attackCommand);
                invoker.ExecuteCommand(attackCommand);
                invoker.ExecuteCommand(defenseCommand);
                invoker.ExecuteCommand(attackCommand);
                invoker.ExecuteCommand(defenseCommand);

                invoker.Replay();
            }
        }
    }

Command 클래스들은 Receiver(Player)를 가지고 있고 invoker를 통해서 Command를 실행시켰다.

 

 

실행 결과

예시 실행 겨로가


커맨드 패턴의 장단점

 

Pros Cons
- Decoupling
- Flexibility and Extensibility
- Undo/Redo and Transactional Support
- Centralized
- Increased Complexity
- Potential Overhead
- Limited Command Semantics

 

장점 (특징)

  • Decoupling
    요청의 발신자(Client)와 수신자(Receiver)를 분리하여, 발신자는 수신자의 특정 세부 구현 정보를 알 필요가 없음 (캡슐화되어 커맨드 요청)
  • Flexibility and Extensibility
    Command 인터페이스나 추상 클래스를 통해 새로운 커맨드를 쉽게 추가하여 유연하고 확장 가능성이 높은 프로그래밍을 할 수 있음

  • Undo/Redo and Transactional Support
    실행 취소 및 다시 실행 기능을 구현하기에 적합한 패턴이며, 실행된 명령을 저장 및 컨트롤하기가 쉬워서 여러 명령을 단일 작업 단위로 실행되는 트랜잭션으로 그룹화하여 사용할 수도 있음

  • Centralized
    Invoker를 통해 명령을 요청하므로, 중앙 집중식 제어 및 관리를 하여 명령을 대기열에 넣거나 예약하거나 우선순위를 조정(스케쥴링)하여 명령을 실행 시킬 수 있음

단점

  •  Increased Complexity
    Command에 대한 클래스도 각각 생성을 해주어야 되므로 클래스 수가 많아질 수 있어 코드베이스의 복잡성이 증가할 수 있음

  • Potential Overhead
    Invoker를 통해 명령을 요청하고 명령을 개체로 캡슐화하기 때문에 약간의 오버헤드가 발생할 수 있으며 명령이 많은 시스템일 수록 성능 문제가 생길 수 있음

  • Limited Command Semantics
    명령을 개체로 추상화 하므로 명령의 표현력이 제한되어 이 명령 개체만 사용하여 명령 간 복잡한 작업이나 상호작용이 어려울 수 있어 추가적인 개체를 활용해야 됨


Summary

 

커맨드 패턴은 명령을 실행하는데 있어서 분리, 유연성 및 확장성을 제공하는 디자인 패턴으로 Undo/Redo 등의 기능을 구현할 수 있고 트랜잭션 동작을 지원한다. 다만, 많은 명령을 모두 커맨드 패턴을 통해 수행하면 잠재적인 오버헤드가 발생할 수 있고 복잡한 명령을 만들기 어려울 수 도 있다.

 

따라서 커맨드 패턴은 개발 중인 시스템에 맞게 요구사항과 복잡성에 따라 적합하게 사용하자.

'etc' 카테고리의 다른 글

스택과 힙 (stack & heap), 메모리 레이아웃  (0) 2023.06.25
Event Bus Pattern [이벤트 버스 패턴]  (0) 2023.06.18
Observer Pattern [관찰자 패턴]  (0) 2023.06.11
Singleton Design Pattern [싱글턴 패턴]  (0) 2023.06.11
OSI, TCP/IP  (0) 2023.06.04