특정 작업을 객체로 캡슐화
하여 실행, 취소 및 재실행할 수 있도록 하는 패턴입니다. Unreal Engine의 게임 입력 처리 또는 특정 작업 실행과 관련된 기능에서 사용할 수 있습니다
커맨드 패턴의 장점
보안과 안정성
: 객체의 내부 상태와 구현 세부사항을 감춤으로써 외부에서의 무분별한 접근을 방지합니다. 이는 데이터 무결성을 보호하고 의도치 않은 변경으로부터 객체를 보호하는 데 도움이 됩니다.추상화
: 객체의 내부 구현을 감추면 외부에 제공되는 인터페이스만 공개됩니다. 이를 통해 외부 사용자는 해당 객체가 어떻게 작동하는지에 대해 신경 쓰지 않고 인터페이스를 통해 상호 작용할 수 있습니다.모듈화와 유지보수성
: 객체가 외부에 영향을 미치지 않고 내부 구현을 변경할 수 있으므로, 객체의 내부 변경이 다른 부분에 영향을 미치지 않고 개선할 수 있습니다. 이는 코드를 모듈화하여 유지보수성을 향상시키는 데 도움이 됩니다.재사용성
: 객체의 외부 인터페이스를 사용하여 동일한 객체를 다양한 상황에서 재사용할 수 있습니다. 외부에 노출된 인터페이스를 통해 객체의 재사용성을 높일 수 있습니다.커맨드 패턴의 구성 요소
Command 인터페이스
: 실행할 명령을 정의하는 인터페이스로, 일반적으로 Execute() 메서드를 가집니다.ConcreteCommand 클래스
: Command 인터페이스를 구현하는 구체적인 클래스입니다. 실제로 실행될 메서드(행동)를 정의하고, 해당 메서드를 호출할 Receiver 객체를 참조합니다.Receiver 클래스
: 실제로 작업을 수행하는 객체입니다. ConcreteCommand 클래스는 Receiver 객체의 메서드를 호출하여 작업을 수행합니다.Invoker 클래스
: ConcreteCommand 객체를 보유하고, 명령을 실행하거나 취소하는 역할을 합니다.Client
: ConcreteCommand 객체와 Receiver 객체를 생성하고, Invoker에 연결하는 클라이언트입니다.Command.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "Command.generated.h"
UINTERFACE(MinimalAPI)
class UCommand : public UInterface
{
GENERATED_BODY()
};
class PUZZLESTUDY_API ICommand
{
GENERATED_BODY()
public:
// 명령 실행
virtual void Excute() = 0;
// 명령 취소 --> Puzzle 게임 현상 ~= 블록 되돌아가기
virtual void Undo() = 0;
//virtual ~ICommand() = default;
};
SwapTileCommand.h
#pragma once
#include "CoreMinimal.h"
#include "Command.h"
class ATile;
class PUZZLESTUDY_API SwapTileCommand : public ICommand
{
public:
SwapTileCommand();
~SwapTileCommand();
SwapTileCommand(ATile* InFirstTile, ATile* InSecondTile);
private:
ATile* FirstTile;
ATile* SecondTile;
FName FirstTileOrigineType;
FName SecondTileOrigineType;
public:
// ICommand의 인터페이스 함수들 Imprement ~
virtual void Excute() override;
virtual void Undo() override;
};
SwapTileCommand.cpp
#include "SwapTileCommand.h"
#include "Tile.h"
SwapTileCommand::SwapTileCommand()
{
}
SwapTileCommand::~SwapTileCommand()
{
}
SwapTileCommand::SwapTileCommand(ATile* InFirstTile, ATile* InSecondTile)
: FirstTile(InFirstTile), SecondTile(InSecondTile)
{
FirstTileOrigineType = FirstTile->TileType;
SecondTileOrigineType = SecondTile->TileType;
}
void SwapTileCommand::Excute()
{
// Swap , 위치 Vector ( Location ) , 행력 ( Row, Col -> int , int )
FName Temp = FirstTile->TileType;
FirstTile->TileType = SecondTile->TileType;
SecondTile->TileType = Temp;
}
void SwapTileCommand::Undo()
{
FirstTile->TileType = FirstTileOrigineType;
SecondTile->TileType = SecondTileOrigineType;
}
TileCommandInvoker.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Command.h"
#include "TileCommandInvoker.generated.h"
UCLASS()
class PUZZLESTUDY_API ATileCommandInvoker : public AActor
{
GENERATED_BODY()
private:
TArray<ICommand*> CommandHistory;
public:
ATileCommandInvoker();
~ATileCommandInvoker();
// 커맨드 실행, History( TArray )에 저장
void ExcuteCommand(ICommand* Command);
// 마지막 명령을 취소 ( Undo )
void UndoLastCommand();
};
TileCommandInvoker.cpp
#include "TileCommandInvoker.h"
ATileCommandInvoker::ATileCommandInvoker()
{
PrimaryActorTick.bCanEverTick = true;
}
ATileCommandInvoker::~ATileCommandInvoker()
{
}
void ATileCommandInvoker::ExcuteCommand(ICommand* Command)
{
// 예시가 단순해서 ( X ), 캡슐화와 커맨드 패턴을 잘 지향하였다면, 거의 디커플링이 완벽
Command->Excute();
CommandHistory.Push(Command);
// add 의 alias가 push
}
void ATileCommandInvoker::UndoLastCommand()
{
// 삭제 안하고, '없음' '공백'
// 되돌아가기 -> 되돌아갈 히스토리가 있어야된다
if (CommandHistory.Num() != 0)
{
ICommand* LastCommand = CommandHistory.Last();
LastCommand->Undo();
CommandHistory.Pop();
//CommandHistory.RemoveAll();
}
}