📕 4. 객체지향 디자인 패턴
유니티에서 사용하면 좋을 디자인 패턴만 명시한다.
📄 5. Command Pattern 행동 패턴
1. 커맨드 패턴의 특징
-
함수 호출(요청)을 저장하는 객체 형태로 캡슐화
- Excute와 Undo를 캡슐화 하여 관리하는데 유용한 패턴이다.
- Command라는 추상적인 단위로 ConcreteCommand라는 클래스를 만들 수 있고.
- 함수호출이란, 리시버에 의존하여 리시버의 함수를 수행한다 라는것.
-
함수 호출 타이밍 조절 & 로깅
- 커맨드를 콜렉션(Queue, Stack, List)으로 관리할 수 있다.
실행 타이밍에 순서를 둘 수 있으니 턴제 게임에도 유용하다.
- 커맨드를 콜렉션(Queue, Stack, List)으로 관리할 수 있다.
-
굳이 그 클래스에 정의되지는 않지만.
- 특정한 로직을 수행하는 메소드를 외부에 정의하도록 하여
- 함수에 사용될 데이터와 객체만 있다면 새로운 메소드를 만들고 다른곳에서 사용 가능
2. 왜 사용하는가
① 함수 호출(요청을) 캡슐화 하기 위해
-
1. 책임 분배로, 작업을 여러개로 분리 2. 결합도를 낮춘다. 3. 재사용성이 높은 구조로 사용하는 패턴이다. 4. 커맨드 객체를 콜백으로 사용하는
② 명령의 실행의 히스토리를 관리한다.
③ 명령의 비동기 처리 및 큐
④ 공통점과 차이점
-
델리게이트와 커맨트 패턴의 공통점은. 콜백 매커니즘 사용할때 델리게이트, 커맨드패턴 둘다 사용 가능하다. 메소드를 객체화 하여, 컨테이너로 관리할 수 있는 형태 차이점은 : 델리게이트 델리게이트는 함수 포인터를 참조하는 타입으로 메소드 참조를 저장하고 호출 할 수 있다. 이벤트 매커니즘을 구현하는데 사용 가능하다. 이건 옵저버 패턴과도 일맥 상통하는 부분이다. 한 객체의 상태가 변경될때, 이를 의존하는 다수의 객체들에게 알리고자 할때 사용가능하다. 비동기 프로그래밍 차이점은 : 커맨드 패턴은 Receiver라는 의존하는 객체를 참조하여 그 객체가 가지고 있는 메소드를 실행하는 매커니즘이다. 따라서 클라이언트, 커맨드, 리시버간 의존성이 있다는것이다.
⑤ 사용 예시 EX )
-
1. 턴제 게임 * 함수 호출 커맨드를 컨테이너로 관리하는 Invoker 사용 2. 함수 호출 Logger * Undo/Redo * ReplaySystem 3. 컨트롤러 키보드 매핑
3. 커맨드 패턴 구성요소
-
Client
- ConcreteCommand의 객체 생성을 담당.
그리고 그 명령을 수행하는 Receiver를 참조하는 관계이다.
- ConcreteCommand의 객체 생성을 담당.
-
Command :
- 적어도 ConcreteCommand면 가져야할 메소드를 것을 정의한 인터페이스 || 추상클래스
- Execute를 수행
- Undo/Redo를 수행
- 적어도 ConcreteCommand면 가져야할 메소드를 것을 정의한 인터페이스 || 추상클래스
-
ConcreteCommand
- Command를 구현한 객체
- Receiver를 참조하여 메소드를 의존하여 특수화 된 객체들이다.
- 즉 이 구체 커맨드의 목적은 자기 데이터를 가지는 일 없고, 오직 외부 Reciever에 특수한 동작을 시키는데 구성된 녀석이다.
- 클래스 네이밍은 행동에대한 동사형 적기 (Ex. TurnTvOn, JumpCommand)
-
Receiver :
- Command가 의존하는, 혹은 의존 주입이 되는 객체
-
Caller || Invoker :
- Replay 를 수행
- Client클래스에서 생성한 ConcreteCommand 객체들을
- 저장하고, 2. 실행의 책임이 있다.
- 선택적으로 커맨드객체를 컬렉션(Queue, Stack, List)로 보관하고,
명령 패턴을 쓰다보면 수많은 Command 클래스를 만들어야 할 수 있다.
이럴 때에는 상위 클래스에 여려 가지 편의를 제공하는 상위 레벨 메서드를 만들어 놓은 뒤에
필요하면 하위 클래스에서 원하는 작동을 재정의할 수 있게 하면 좋다.
이러면 명령 클래스의 execute 메서드가 하위 클래스 샌드박스 패턴으로 발전하게 된다.
4. 커맨드 패턴의 예시
레스토랑에 온 소님을 예시로 들어보자
"손님"(Client) 는 "웨이터"(Invoker) 에게
"음식의 제작(FoodOrderConcreteCommand)" 을 "말/요청(Command)" 했다.
"음식을 만드는 쉐프(Receiver)" 는 주문에 따라 음식을 만든다.
ⓓ 예시
📂IHeartGameDev Command (C#)📂
/*ICommnad.cs*/
public interface ICommand {
void Execute();
void Redo();
void Redo();
}
/*ConcreteCommands.cs*/
public class TurnOnCommnad() : ICommand {
LightBulb mLbRef;
//Previous
public TurnOnCommnad(LightBulb lb) { mLbRef = lb; }
public Execute() => mLbRef.TurnOn();
}
public class TurnOffCommnad() : ICommand {
LightBulb mLbRef;
//Previous
public TurnOffCommnad(LightBulb lb) { mLbRef = lb; }
public Execute() => mLbRef.TurnOff();
}
/*LightBulb.cs*/
public class LightBulb : MonoBehaviour {
public void TurnOn();
public void TurnOff();
}
/*Client.cs*/
public class Client : MonoBehaviour{
public LightBulb lb;
void Update() {
if(Input.GetKeyDown(KeyCode.Space)) {
ICommand storedCommand = new TurnOnCommand(lb);
storedCommand.Execute();
}
}
}
public class LightSwitch {
ICommand _onCommand, _offCommand;
public void AddCommand();
public void PowerOn() { _onCommand.Execute(); }
public void PowerOff() { _onCommand.Execute(); }
}
📂Undo Command (C#)📂
namespace DesignPattern
{
//Base class for the commands
//This class should always look like this to make it more general, so no constructors, parameters, etc!!!
public abstract class Command
{
public abstract void Execute();
public abstract void Undo();
}
}
📂Command Pattern 정석적인 예시 (C#)📂
namespace DesignPattern
{
namespace Device
{
public interface ElectronicDeviceReceiver
{
public void On();
public void Off();
public void VolumeUp();
public void VolumeDown();
}
//리시버
public class Television : ElectronicDeviceReceiver {
private int volume = 0;
public void On() => Console.WriteLine("TV is On");
public void Off() => Console.WriteLine("TV is Off");
public void VolumeUp() => this.volume++;
public void VolumeDown() => this.volume--;
}
//리시버
public class Radio : ElectronicDeviceReceiver {
private int volume = 0;
public void On() => Console.WriteLine("Radio is On");
public void Off() => Console.WriteLine("Radio is Off");
public void VolumeUp() => this.volume++;
public void VolumeDown() => this.volume--;
}
}
namespace Command
{
public interface Command {
public void Execute();
}
//콘크리트 커맨드
public class TurnTvOn : Command {
private ElectronicDevice electronicDevice;
public TurnTvOn(ElectronicDevice electronicDevice){
this.electronicDevice = electronicDevice;
}
public void Execute() => electronicDevice.On();
}
//콘크리트 커맨드
public class TurnTvOff : Command {
private ElectronicDevice electronicDevice;
public TurnTvOff(ElectronicDevice electronicDevice){ this.electronicDevice = electronicDevice; }
public void Execute() => electronicDevice.Off();
}
public class TurnAllInvoker : Command {
List<ElectronicDevice> electroDevices;
public TurnAllInvoker() { this.electroDevices = new List<ElectronicDevice>(); }
public TurnAllInvoker AddDevices(ElectronicDevice electronicDevice){
this.electroDevices.Add(electronicDevice);
return TurnAllInvoker;
}
public void Execute() => foreach(ElectronicDevice E in this.electroDevices){E.Off();}
}
public class DeviceButtonInvoker{
Command theCommand;
public DeviceButton(Command command){ this.theCommand = command; }
public void Press() => theCommand.Execute();
}
}
public class Client() {
ElectronicDevice tv = new Television();
TurnTvOn onCommand = new TurnTvOn(tv);
TurnTvOff offCommand = new TurnTvOff(tv);
DeviceButton onButton= new DeviceButton(onCommand);
onButton.Press();
onButton = new DeviceButton(offCommand);
onButton.Press();
ElectronicDevice radio = new Radio();
TurnAllDevices allOffCommand= new TurnAllDevices();
allOffCommand.AddDevices(radio).AddDevices(tv);
onButton = new DeviceButton(allOffCommand);
onButton.Press();
}
}
📂Command Pattern 키 변경 (C#)📂
using System.Collections;
using UnitEngine;
namespace DesignPattern
{
public interface Command {
public void Execute(GameObject obj);
}
public class ConcreteCommandAttack : Command {
public void Execute(Gameobject obj) { Attack(obj); }
void Attack(Gameobject obj){
obj.transform.Translate(Vector3.forword);
}
}
public class ConcreteCommandDefense : Command {
public void Execute(GameObject obj){ Defense(obj); }
void Defense(GameObject obj)
{
Debug.Log(obj.name + " Defense");
obj.transform.Translate (Vector3.back);
}
}
public class PlayerKeyClient : MonoBehaviour {
CommandKey btnA, btnB;
void Start () { SetCommand(); }
void SetCommand()
{
btnA = new CommandAttack();
btnB = new CommandDefense();
}
public void BtnCommandA() => btnA.Execute(gameObject); // 이 스크립트가 붙은 오브젝트를 공격하게 함
public void BtnCommandB() => btnB.Execute(gameObject); // 이 스크립트가 붙은 오브젝트를 방어하게 함
}
}
참조
'CS > SW 공학' 카테고리의 다른 글
| 니앙팽이 - 객체지향(OOP) | 4-8 | 행동패턴 - State pattern (0) | 2023.02.02 |
---|---|
| 니앙팽이 - 객체지향(OOP) | 4-7 | 행동패턴 - Observer pattern (0) | 2023.02.02 |
| 니앙팽이 - 객체지향(OOP) | 4-5 | 구조패턴 - Flyweight pattern (0) | 2023.02.02 |
| 니앙팽이 - 객체지향(OOP) | 4-4 | 구조패턴 - Decorator pattern (0) | 2023.02.02 |
| 니앙팽이 - 객체지향(OOP) | 4-3 | 구조패턴 - Composite pattern (0) | 2023.02.02 |