커맨드 패턴(Command Pattern) 이해하기
커맨드 패턴은 요청을 객체로 캡슐화하여 요청의 실행, 취소, 재실행 등의 작업을 유연하게 처리할 수 있도록 하는 행동 디자인 패턴입니다. 이 패턴은 요청의 발신자와 수신자를 분리하여, 요청의 실행 방법과 시점을 독립적으로 관리할 수 있게 해줍니다.
커맨드 패턴은 버튼 클릭, 작업 취소/재실행, 작업 기록과 같은 행동을 명령 객체로 캡슐화하여 다양한 애플리케이션에서 널리 사용됩니다. 이번 포스트에서는 커맨드 패턴의 개념과 구조, 구현 방법, 장단점 및 사용 사례를 구체적인 예제와 함께 알아보겠습니다.
커맨드 패턴이란?
커맨드 패턴은 명령(Command)을 객체로 캡슐화하여 요청의 실행과정을 호출자(Invoker)와 수신자(Receiver) 간에 분리하는 디자인 패턴입니다. 이 패턴을 사용하면 요청을 큐에 저장하거나, 로그로 기록하거나, 나중에 재실행할 수도 있습니다.
주요 개념은 요청을 실행하는 방법(명령의 실행 코드)을 캡슐화하여, 요청의 발신자와 수신자를 분리하는 것입니다.
커맨드 패턴의 구조
커맨드 패턴은 다음과 같은 구성 요소로 이루어집니다.
- Command (명령 인터페이스):
- 실행될 작업을 정의하는 인터페이스로, 일반적으로 Execute() 메서드를 포함합니다.
- ConcreteCommand (구체적인 명령):
- Command 인터페이스를 구현하며, 작업을 수행하기 위해 필요한 요청을 캡슐화합니다.
- Receiver (수신자):
- 실제 작업을 수행하는 객체로, ConcreteCommand가 이 객체의 메서드를 호출하여 요청을 처리합니다.
- Invoker (호출자):
- 명령 객체를 호출하여 요청을 실행합니다. 호출자는 요청의 구체적인 실행 방법을 알 필요 없이 명령 객체를 통해 작업을 수행합니다.
- Client (클라이언트):
- 명령 객체를 생성하고, 호출자에게 전달합니다.
커맨드 패턴의 구현 방법
예제 코드: 스마트 홈 제어 시스템
스마트 홈 제어 시스템에서 커맨드 패턴을 활용하여 조명과 에어컨을 제어하는 예제를 구현해보겠습니다.
// Command 인터페이스
public interface ICommand
{
void Execute();
void Undo();
}
// Receiver 클래스 (수신자): 조명
public class Light
{
public void TurnOn()
{
Console.WriteLine("Light is ON");
}
public void TurnOff()
{
Console.WriteLine("Light is OFF");
}
}
// Receiver 클래스 (수신자): 에어컨
public class AirConditioner
{
public void TurnOn()
{
Console.WriteLine("Air Conditioner is ON");
}
public void TurnOff()
{
Console.WriteLine("Air Conditioner is OFF");
}
}
// ConcreteCommand 클래스: 조명 켜기
public class LightOnCommand : ICommand
{
private Light _light;
public LightOnCommand(Light light)
{
_light = light;
}
public void Execute()
{
_light.TurnOn();
}
public void Undo()
{
_light.TurnOff();
}
}
// ConcreteCommand 클래스: 조명 끄기
public class LightOffCommand : ICommand
{
private Light _light;
public LightOffCommand(Light light)
{
_light = light;
}
public void Execute()
{
_light.TurnOff();
}
public void Undo()
{
_light.TurnOn();
}
}
// ConcreteCommand 클래스: 에어컨 켜기
public class AirConditionerOnCommand : ICommand
{
private AirConditioner _airConditioner;
public AirConditionerOnCommand(AirConditioner airConditioner)
{
_airConditioner = airConditioner;
}
public void Execute()
{
_airConditioner.TurnOn();
}
public void Undo()
{
_airConditioner.TurnOff();
}
}
// ConcreteCommand 클래스: 에어컨 끄기
public class AirConditionerOffCommand : ICommand
{
private AirConditioner _airConditioner;
public AirConditionerOffCommand(AirConditioner airConditioner)
{
_airConditioner = airConditioner;
}
public void Execute()
{
_airConditioner.TurnOff();
}
public void Undo()
{
_airConditioner.TurnOn();
}
}
// Invoker 클래스: 리모컨
public class RemoteControl
{
private ICommand _command;
public void SetCommand(ICommand command)
{
_command = command;
}
public void PressButton()
{
_command.Execute();
}
public void PressUndoButton()
{
_command.Undo();
}
}
// Client 코드
class Program
{
static void Main()
{
// 수신자 생성
Light livingRoomLight = new Light();
AirConditioner airConditioner = new AirConditioner();
// 명령 객체 생성
ICommand lightOnCommand = new LightOnCommand(livingRoomLight);
ICommand lightOffCommand = new LightOffCommand(livingRoomLight);
ICommand airConditionerOnCommand = new AirConditionerOnCommand(airConditioner);
ICommand airConditionerOffCommand = new AirConditionerOffCommand(airConditioner);
// 호출자 생성
RemoteControl remoteControl = new RemoteControl();
// 조명 켜기
remoteControl.SetCommand(lightOnCommand);
remoteControl.PressButton();
remoteControl.PressUndoButton();
// 에어컨 끄기
remoteControl.SetCommand(airConditionerOffCommand);
remoteControl.PressButton();
remoteControl.PressUndoButton();
}
}
실행 결과
Light is ON
Light is OFF
Air Conditioner is OFF
Air Conditioner is ON
설명
- Receiver:
- Light와 AirConditioner 클래스는 실제 작업을 수행하는 수신자입니다.
- ConcreteCommand:
- LightOnCommand, LightOffCommand, AirConditionerOnCommand, AirConditionerOffCommand 클래스는 각각 조명과 에어컨의 동작을 캡슐화한 명령 객체입니다.
- Invoker:
- RemoteControl 클래스는 명령 객체를 호출하여 작업을 수행하거나 실행 취소(Undo)를 요청합니다.
- Client:
- Program 클래스는 명령 객체를 생성하고 호출자(리모컨)에게 전달하여 작업을 수행합니다.
커맨드 패턴의 장단점
장점
- 요청 캡슐화:
- 요청을 객체로 캡슐화하여, 요청을 큐에 저장하거나 나중에 재실행할 수 있습니다.
- 확장성:
- 새로운 명령 객체를 추가하거나 기존 명령을 변경하는 작업이 쉽습니다.
- 작업 취소/재실행:
- Undo() 메서드를 통해 작업을 쉽게 취소하거나 재실행할 수 있습니다.
- 발신자와 수신자 분리:
- 호출자와 작업을 실제로 수행하는 객체를 분리하여 느슨한 결합을 유지합니다.
단점
- 클래스 증가:
- 명령마다 별도의 클래스가 필요하므로 클래스 수가 증가할 수 있습니다.
- 복잡성 증가:
- 요청을 캡슐화하는 과정에서 코드의 복잡도가 증가할 수 있습니다.
언제 커맨드 패턴을 사용해야 할까?
커맨드 패턴은 다음과 같은 상황에서 유용합니다.
- 작업 취소/재실행이 필요한 경우:
- 작업 이력을 관리하거나 Undo()/Redo() 기능을 구현해야 할 때.
- 요청을 큐에 저장해야 하는 경우:
- 요청을 나중에 실행하거나, 요청의 순서를 변경할 수 있어야 할 때.
- 발신자와 수신자의 결합을 줄여야 할 때:
- 호출자와 수신자가 독립적으로 동작해야 하거나, 호출자가 요청의 세부 사항을 알 필요가 없을 때.
- 매크로 명령이 필요한 경우:
- 여러 작업을 순차적으로 실행하는 명령을 캡슐화해야 할 때.
커맨드 패턴과 관련 패턴 비교
커맨드 패턴은 요청을 객체로 캡슐화하여 발신자와 수신자를 분리하는 데 초점을 둡니다. 비슷한 행동 패턴으로는 책임 연쇄 패턴과 전략 패턴이 있습니다.
- 책임 연쇄 패턴:
- 요청을 처리할 수 있는 객체를 체인 형태로 연결하여, 요청이 처리될 때까지 전달합니다.
- 요청의 처리 과정을 동적으로 변경할 수 있습니다.
- 전략 패턴:
- 작업을 수행하는 방법을 객체로 캡슐화하여 동적으로 교체할 수 있도록 합니다.
- 특정 작업 알고리즘을 변경할 수 있습니다.
따라서, 요청의 실행/취소가 필요한 경우에는 커맨드 패턴, 요청의 처리 과정을 동적으로 변경해야 할 경우에는 책임 연쇄 패턴, 동적으로 알고리즘을 교체해야 할 경우에는 전략 패턴을 사용하는 것이 적절합니다.
마무리하며
커맨드 패턴은 요청을 객체로 캡슐화하여 발신자와 수신자를 분리하고, 요청의 실행, 취소, 재실행 등을 유연하게 처리할 수 있는 강력한 패턴입니다. 작업 이력 관리, 버튼 클릭 처리, 매크로 명령 구현 등 다양한 상황에서 활용할 수 있어 소프트웨어 설계에서 자주 사용됩니다.
취소/재실행 기능이나 요청 처리의 유연성이 필요한 시스템에서 커맨드 패턴을 활용해보세요. 이를 통해 코드의 확장성과 유지보수성을 크게 향상시킬 수 있습니다.
'Thinking > Concept' 카테고리의 다른 글
이터레이터 패턴(Iterator Pattern) 이해하기 (0) | 2024.11.16 |
---|---|
인터프리터 패턴(Interpreter Pattern) 이해하기 (0) | 2024.11.16 |
책임 연쇄 패턴(Chain of Responsibility Pattern) 이해하기 (0) | 2024.11.16 |
프록시 패턴(Proxy Pattern) 이해하기 (0) | 2024.11.15 |
플라이웨이트 패턴(Flyweight Pattern) 이해하기 (0) | 2024.11.15 |