재우니의 블로그

https://unsplash.com/ko/%EC%82%AC%EC%A7%84/xLagMfiA8_4

Command Pattern

 

Command 패턴은 객체의 행위나 요청을 캡슐화하는 디자인 패턴입니다. 이를 통해 클라이언트와 요청을 실행하는 객체를 분리할 수 있습니다. 

 

사용 예제: Command  패턴은 C# 코드에서 매우 일반적입니다. 대부분 액션으로 UI 요소를 매개변수화하기 위한 콜백의 대안으로 사용됩니다. 또한 queueing tasks, tracking operations history 등에 사용되기도 합니다.

식별: Command  패턴은 Command  구현이 생성하는 동안 캡슐화된 다른 추상/인터페이스 유형(발신자)의 구현에서 메서드를 호출하는 추상/인터페이스 유형(수신자)의 행동 메서드로 인식할 수 있습니다. Command 클래스는 일반적으로 특정 작업으로 제한됩니다.

 

 

아래 간단한 예시로 콘솔에서 실행되는 작업을 구현하겠습니다.  예제에서는 ICommand 인터페이스와 몇 가지 콘솔 출력 명령이 구현되어 있습니다. CommandInvoker 클래스는 이 명령들을 모아서 실행할 수 있습니다.

 

using System;
using System.Collections.Generic;

public interface ICommand
{
    void Execute();
}

public class PrintHelloCommand : ICommand
{
    public void Execute()
    {
        Console.WriteLine("Hello!");
    }
}

public class PrintGoodbyeCommand : ICommand
{
    public void Execute()
    {
        Console.WriteLine("Goodbye!");
    }
}

public class PrintDateCommand : ICommand
{
    public void Execute()
    {
        Console.WriteLine($"Today is {DateTime.Now.ToShortDateString()}");
    }
}

public class CommandInvoker
{
    private readonly List<ICommand> _commands;

    public CommandInvoker()
    {
        _commands = new List<ICommand>();
    }

    public void AddCommand(ICommand command)
    {
        _commands.Add(command);
    }

    public void ExecuteCommands()
    {
        foreach (var command in _commands)
        {
            command.Execute();
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        ICommand helloCommand = new PrintHelloCommand();
        ICommand goodbyeCommand = new PrintGoodbyeCommand();
        ICommand dateCommand = new PrintDateCommand();

        var invoker = new CommandInvoker();
        invoker.AddCommand(helloCommand);
        invoker.AddCommand(dateCommand);
        invoker.AddCommand(goodbyeCommand);

        invoker.ExecuteCommands();
    }
}

 

결과물

 

더보기

Hello!
Today is 2023-07-06
Goodbye!

 

위의 예제에서 각 Command 클래스(PrintHelloCommand, PrintGoodbyeCommand, PrintDateCommand)는 ICommand 인터페이스를 구현합니다. Execute 메서드가 실행되면 각각의 작업을 수행합니다. CommandInvoker는 ICommand 인터페이스를 구현하는 Command 객체를 저장하고, ExecuteCommands 메서드를 호출할 때 이 Command 들을 실행합니다. 이렇게 하면 어떤 Command 이든 동일한 수행 방식으로 코드를 실행할 수 있습니다.

 

 

 

 

Command  이용하여 StarCraft 게임 활용해 보기

 

Command  이용하여 StarCraft 게임의 유닛을 생성하고 싸워서 없애는 간단한 예제 코드를 드리겠습니다. 이 예제에서는 Terran과 Protoss 두 종족의 유닛을 생성하고, 서로 공격하여 HP가 0이 되면 삭제됩니다.

 

using System;
using System.Collections.Generic;

public interface IUnit
{
    string Name { get; set; }
    int Hp { get; set; }
    int AttackPower { get; set; }
    void Attack(IUnit target);
}

public class TerranUnit : IUnit
{
    public string Name { get; set; }
    public int Hp { get; set; }
    public int AttackPower { get; set; }

    public TerranUnit(string name, int hp, int attackPower)
    {
        Name = name;
        Hp = hp;
        AttackPower = attackPower;
    }

    public void Attack(IUnit target)
    {
        Console.WriteLine($"{Name} attacks {target.Name}!");
        target.Hp -= AttackPower;
    }
}

public class ProtossUnit : IUnit
{
    public string Name { get; set; }
    public int Hp { get; set; }
    public int AttackPower { get; set; }

    public ProtossUnit(string name, int hp, int attackPower)
    {
        Name = name;
        Hp = hp;
        AttackPower = attackPower;
    }

    public void Attack(IUnit target)
    {
        Console.WriteLine($"{Name} attacks {target.Name}!");
        target.Hp -= AttackPower;
    }
}

// Command 패턴을 적용할 차례입니다. 
// 다음과 같이 Command 인터페이스를 정의 후 각각의 클래스에 적용해 보죠.
public interface ICommand
{
    bool CanExecute { get; }
    void Execute();
}

public class AttackCommand : ICommand
{
    private IUnit _attacker;
    private IUnit _defender;

    public bool CanExecute => _attacker.Hp > 0 && _defender.Hp > 0;

    public AttackCommand(IUnit attacker, IUnit defender)
    {
        _attacker = attacker;
        _defender = defender;
    }

    public void Execute()
    {
        _attacker.Attack(_defender);
        if (_defender.Hp <= 0)
        {
            Console.WriteLine($"{_defender.Name} has been destroyed!");
        }
    }
}

// Command 실행 클래스를 작성
public class CommandInvoker
{
    private readonly List<ICommand> _commands;

    public CommandInvoker()
    {
        _commands = new List<ICommand>();
    }

    public void AddCommand(ICommand command)
    {
        _commands.Add(command);
    }

    public void ExecuteCommands()
    {
        foreach (var command in _commands)
        {
            if (command.CanExecute)
            {
                command.Execute();
            }
        }
    }
}


// 작성한 클래스들을 통해 게임을 실행
class Program
{
    static void Main(string[] args)
    {
        IUnit marine = new TerranUnit("Marine", 50, 10);
        IUnit zealot = new ProtossUnit("Zealot", 60, 15);

        ICommand marineAttack = new AttackCommand(marine, zealot);
        ICommand zealotAttack = new AttackCommand(zealot, marine);

        CommandInvoker invoker = new CommandInvoker();
        invoker.AddCommand(marineAttack);
        invoker.AddCommand(zealotAttack);

        while (marine.Hp > 0 && zealot.Hp > 0)
        {
            invoker.ExecuteCommands();
        }
    }
}

 

위의 코드는 각 유닛이 공격을 실행할 때마다 서로의 HP를 확인하여 유닛이 파괴되었는지 판단할 수 있게 했습니다. 이제 has been destroyed! 메시지가 출력됩니다.

 

 

 

참고 사이트

 

https://refactoring.guru/design-patterns/command/csharp/example#example-0

 

Command in C# / Design Patterns

/ Design Patterns / Command / C# Command is behavioral design pattern that converts requests or simple operations into objects. The conversion allows deferred or remote execution of commands, storing command history, etc. Complexity: Popularity: Usage exa

refactoring.guru