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();
}
}
위의 예제에서 각 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! 메시지가 출력됩니다.