본문 바로가기
개발/Architecture & Design Pattern

C++ - 커맨드 패턴 (Command Pattern)

by 피로물든딸기 2024. 2. 23.
반응형

C, C++ 전체 링크

Architecture & Design Pattern 전체 링크

 

참고

- 클래스 다이어그램 그리기

- 스마트 포인터 : unique_ptr

 

커맨드 패턴 (Command Pattern) - 행동 패턴

- 행동을 캡슐화하여 다른 객체로 전달하는 패턴

- 요청을 객체로 캡슐화하여 매개변수화된 메서드 호출, 메서드 실행 또는 연산을 지원한다.

 

구현

CommandInterface : execute() 메서드와 그에 필요한 다른 메서드(undo 등)를 정의

- ConcreteCommand : Command 인터페이스를 구현하여 실제로 실행될 작업을 구현

- Invoker : 클라이언트로부터 커맨드 객체를 받아서 실행하는 클래스, 클라이언트와 커맨드를 분리하는 역할

- Receiver : 실제로 커맨드에 의해 수행되는 작업을 정의하는 클래스

- 클라이언트는 ConcreteCommand를 생성하고 Receiver를 설정한다.

 

장점

- SRP(단일 책임 원칙), 요청을 호출하는 객체와 실제 작업을 처리하는 객체를 분리한다.

OCP(개방-폐쇄 원칙), ConcreteCommand 클래스를 작성하여 추가해서 새로운 요청을 추가하기 쉽다.

- 요청한 작업에 대해 작업 취소 기능을 만들 수 있다.

- 작업 큐를 이용하여 작업들의 지연된 실행을 구현할 수 있다.

- 해당 작업을 로그에 기록하여 복구 시스템을 만들 수 있다.

- 커맨드를 조합하여 새로운 커맨드를 만들 수 있다.

 

단점

- 각각의 명령마다 ConcreteCommand 클래스가 필요해서 많은 클래스를 만들어야 한다.

- 너무 많은 작은 객체들이 생길 경우, 관리가 어려워질 수 있다.

- 발송자와 수신자 사이에 새로운 레이어를 도입하므로 코드가 더 복잡해질 수 있다.


만능 리모컨

 

집 안의 전등, TV, 그 외 여러 가전 기기를 연결할 수 있는 만능 리모컨이 있다.

해당 리모컨은 device를 연결하고, 버튼을 누르면 해당 device가 할 수 있는 action을 실행할 수 있다.

 

예를 들어 LightTelevision은 다음과 같은 action이 가능하다.

class Light
{
public:
	void on() { cout << "Light is On" << endl; }
	void off() { cout << "Light is off" << endl; }
};

class Television
{
public:
	void channelUp() { cout << "Channel Up" << endl; }
	void channelDown() { cout << "Channel Down" << endl; }
	void subtitleOn() { cout << "Subtitle On" << endl; }
	void subtitleOff() { cout << "Subtitle Off" << endl; }
};

 

상황에 따라 deviceaction을 선택한다면 아래와 같은 코드를 작성할 수 있다.

#include <iostream>
#include <string>

using namespace std;

class Light
{
public:
	void on() { cout << "Light is On" << endl; }
	void off() { cout << "Light is off" << endl; }
};

class Television
{
public:
	void channelUp() { cout << "Channel Up" << endl; }
	void channelDown() { cout << "Channel Down" << endl; }
	void subtitleOn() { cout << "Subtitle On" << endl; }
	void subtitleOff() { cout << "Subtitle Off" << endl; }
};

int main()
{
	string device = "light";
	string action = "on";

	if (device == "light")
	{
		Light* light = new Light();

		if (action == "on") light->on();
		else if (action == "off") light->off();
		else cout << "Invalid action" << endl;
	}
	else if (device == "television")
	{
		Television* television = new Television();

		if (action == "channelUp") television->channelUp();
		else if (action == "channelDown") television->channelDown();
		else if (action == "subtitleOn") television->subtitleOn();
		else if (action == "subtitleOff") television->subtitleOff();
		else cout << "Invalid action" << endl;
	}
	// or another device ...

	return 0;
}


커맨드 패턴 적용

 

LightTelevision은 행동이 너무 다르다.

즉, 모든 가전기기는 다양한 메서드를 가지고 있다.

모든 가전기기에 대해 main 문을 작성하게 된다면 if / else 구문이 많아지고 유지보수도 힘들어진다.

 

이런 상황을 해결하기 위해 커맨드 패턴을 적용해 보자.

커맨드 패턴을 이용하면 어떤 작업을 요청하는 객체와 작업을 처리하는 객체를 분리할 수 있다.

 

예를 들어 리모컨 버튼 하나에 객체에 대한 명령어 하나가 저장되어 있다면,

리모컨(Invoker)은 버튼을 누르기만 하면 된다.

그리고 그 버튼에 저장된 객체(Command)가전기기(Receiver)의 메서드를 실행시키게 된다.


리모컨 (1개 버튼)

 

커맨드 패턴을 적용하기 위해 버튼이 하나인 리모컨을 만들자.

이 리모컨의 버튼에 특정 명령을 저장할 수 있고, 리모컨의 버튼만 누르면 해당 객체가 행동을 실행하도록 위임한다.

class RemoteControl
{
public:
	RemoteControl() = default;
	void setCommand(Command *command) { button = command; }
	void pressButton() { button->execute(); }
private:
	Command *button;
};

 

각 기기의 메서드는 Command Interface를 구현해야 한다.

class Command
{
public:
	virtual ~Command() = default;
	virtual void execute() = 0;
};

 

예를 들어 LightOnCommandLight켜는 작업을 구현한다.

class Light
{
public:
	void on() { cout << "Light is On" << endl; }
	void off() { cout << "Light is off" << endl; }
};

class LightOnCommand : public Command
{
public:
	LightOnCommand(Light *l) : light(l) { }
	void execute() override { light->on(); }
private:
	Light *light;
};

 

리모컨은 ConcreteCommand를 할당받은 후, pressButton을 이용해 기기를 실행할 수 있다.

int main()
{
	RemoteControl* remote = new RemoteControl(); // Invoker

	Light* light = new Light(); // Receiver
	LightOnCommand* lightOn = new LightOnCommand(light); // ConcreteCommand
	remote->setCommand(lightOn);
	remote->pressButton();

	Television* television = new Television();
	TelevisionSubtitleOn* tvSubtitleOn = new TelevisionSubtitleOn(television);
	remote->setCommand(tvSubtitleOn);
	remote->pressButton();

	return 0;
}

 

전체 코드는 다음과 같다. (Light를 켜는 명령과 자막을 켜는 명령만 구현)

#include <iostream>
#include <string>

using namespace std;

class Command
{
public:
	virtual ~Command() = default;
	virtual void execute() = 0;
};

class Light
{
public:
	void on() { cout << "Light is On" << endl; }
	void off() { cout << "Light is off" << endl; }
};

class LightOn : public Command
{
public:
	LightOn(Light *l) : light(l) { }
	void execute() override { light->on(); }
private:
	Light *light;
};

class Television
{
public:
	void channelUp() { cout << "Channel Up" << endl; }
	void channelDown() { cout << "Channel Down" << endl; }
	void subtitleOn() { cout << "Subtitle On" << endl; }
	void subtitleOff() { cout << "Subtitle Off" << endl; }
};

class TelevisionSubtitleOn : public Command
{
public:
	TelevisionSubtitleOn(Television *tv) : television(tv) { }
	void execute() override { television->subtitleOn(); }
private:
	Television *television;
};

class RemoteControl
{
public:
	RemoteControl() = default;
	void setCommand(Command *command) { button = command; }
	void pressButton() { button->execute(); }
private:
	Command *button;
};

int main()
{
	RemoteControl* remote = new RemoteControl(); // Invoker

	Light* light = new Light(); // Receiver
	LightOn* lightOn = new LightOn(light); // ConcreteCommand
	remote->setCommand(lightOn);
	remote->pressButton();

	Television* television = new Television();
	TelevisionSubtitleOn* tvSubtitleOn = new TelevisionSubtitleOn(television);
	remote->setCommand(tvSubtitleOn);
	remote->pressButton();

	return 0;
}


리모컨 

 

이제 리모컨의 버튼을 여러 개 만들어보자.

on / off (up / down) 버튼이 각각 5개 있다고 가정하면 아래와 같이 리모컨을 구현할 수 있다.

class RemoteControl
{
public:
	RemoteControl() = default;
	void setCommand(Command *upCommand, Command *downCommand, int num)
	{
		upCommands[num] = upCommand;
		downCommands[num] = downCommand;
	}

	void pressUpButton(int num)
	{
		if (upCommands[num] != nullptr)
			upCommands[num]->execute();
	}

	void pressDownButton(int num)
	{
		if (downCommands[num] != nullptr)
			downCommands[num]->execute();
	}

private:
	constexpr static int COMMAND_SIZE = 5;
	shared_ptr<EmptyCommand> empty = make_shared<EmptyCommand>();
	vector<Command *> upCommands = vector<Command *>(COMMAND_SIZE, empty.get());
	vector<Command *> downCommands = vector<Command *>(COMMAND_SIZE, empty.get());
};

 

리모컨의 버튼에 명령이 할당되지 않을 수 있으므로 초기화에 EmptyCommand를 할당한다.

EmptyCommand 또한 Command Interface를 구현해야 하고, 아무것도 하지 않는 execute를 구현한다.

class EmptyCommand : public Command
{
public:
	void execute() override { cout << "This command is empty" << endl; }
};

 

그 외 LightTelevision의 행동에 대한 명령어도 모두 구현한다.

(LightOn/Off, TelevisionChannelUp/Down, TelevisionSubtitleOn/Off)

 

main 문에서 모든 명령을 리모컨에 할당해서 모든 버튼을 눌러보자.

#include <iostream>
#include <string>
#include <memory>
#include <vector>

using namespace std;

class Command
{
public:
	virtual ~Command() = default;
	virtual void execute() = 0;
};

class EmptyCommand : public Command
{
public:
	void execute() override { cout << "This command is empty" << endl; }
};

/* ------------------- Light ------------------- */
class Light
{
public:
	void on() { cout << "Light is On" << endl; }
	void off() { cout << "Light is off" << endl; }
};

class LightOn : public Command
{
public:
	LightOn(Light *l) : light(l) { }
	void execute() override { light->on(); }
private:
	Light *light;
};

class LightOff : public Command
{
public:
	LightOff(Light *l) : light(l) { }
	void execute() override { light->off(); }
private:
	Light *light;
};
/* --------------------------------------------- */

/* ---------------- Television ---------------- */
class Television
{
public:
	void channelUp() { cout << "Channel Up" << endl; }
	void channelDown() { cout << "Channel Down" << endl; }
	void subtitleOn() { cout << "Subtitle On" << endl; }
	void subtitleOff() { cout << "Subtitle Off" << endl; }
};

class TelevisionChannelUp : public Command
{
public:
	TelevisionChannelUp(Television *tv) : television(tv) { }
	void execute() override { television->channelUp(); }
private:
	Television *television;
};

class TelevisionChannelDown : public Command
{
public:
	TelevisionChannelDown(Television *tv) : television(tv) { }
	void execute() override { television->channelDown(); }
private:
	Television *television;
};

class TelevisionSubtitleOn : public Command
{
public:
	TelevisionSubtitleOn(Television *tv) : television(tv) { }
	void execute() override { television->subtitleOn(); }
private:
	Television *television;
};

class TelevisionSubtitleOff : public Command
{
public:
	TelevisionSubtitleOff(Television *tv) : television(tv) { }
	void execute() override { television->subtitleOff(); }
private:
	Television *television;
};

/* -------------------------------------------- */

class RemoteControl
{
public:
	RemoteControl() = default;
	void setCommand(Command *upCommand, Command *downCommand, int num)
	{
		upCommands[num] = upCommand;
		downCommands[num] = downCommand;
	}

	void pressUpButton(int num)
	{
		if (upCommands[num] != nullptr)
			upCommands[num]->execute();
	}

	void pressDownButton(int num)
	{
		if (downCommands[num] != nullptr)
			downCommands[num]->execute();
	}

private:
	constexpr static int COMMAND_SIZE = 5;
	shared_ptr<EmptyCommand> empty = make_shared<EmptyCommand>();
	vector<Command *> upCommands = vector<Command *>(COMMAND_SIZE, empty.get());
	vector<Command *> downCommands = vector<Command *>(COMMAND_SIZE, empty.get());
};

int main()
{
	RemoteControl* remote = new RemoteControl(); // Invoker

	Light* light = new Light(); // Receiver
	LightOn* lightOn = new LightOn(light); // Concrete Command
	LightOff* lightOff = new LightOff(light); 

	Television* television = new Television();
	TelevisionChannelUp* tvChannelUp = new TelevisionChannelUp(television);
	TelevisionChannelDown* tvChannelDown = new TelevisionChannelDown(television);
	TelevisionSubtitleOn* tvSubtitleOn = new TelevisionSubtitleOn(television);
	TelevisionSubtitleOff* tvSubtitleOff = new TelevisionSubtitleOff(television);

	remote->setCommand(lightOn, lightOff, 0);
	remote->setCommand(tvChannelUp, tvChannelDown, 1);
	remote->setCommand(tvSubtitleOn, tvSubtitleOff, 2);

	for (int i = 0; i < 5; i++)
	{
		cout << i << "> up   / on  command : "; remote->pressUpButton(i);
		cout << i << "> down / off command : "; remote->pressDownButton(i);
	}

	return 0;
}


작업 취소하기

 

커맨드 패턴요청한 작업을 취소하는 기능을 구현할 수 있다.

 

이제 리모컨작업 취소 버튼을 만들어서, 취소 버튼을 누르면 실행한 작업이 취소되도록 해보자.

각 명령이 undo 메서드를 모두 구현하도록 Command Interface를 수정하자.

class Command
{
public:
	virtual ~Command() = default;
	virtual void execute() = 0;
	virtual void undo() = 0;
};

 

LightOnexecutelighton 하는 것이기 때문에 undooff를 하면 된다.

class LightOn : public Command
{
public:
	LightOn(Light *l) : light(l) { }
	void execute() override { light->on(); }
	void undo() override { light->off(); }
private:
	Light *light;
};

 

그리고 TV의 자막 On/Off 기능을 OFF / 한글 자막 / 영어 자막으로 세분화해 보자.

class Television
{
public:
	enum class Subtitle { OFF, KOREAN, ENGLISH };
	void channelUp() { cout << "Channel Up" << endl; }
	void channelDown() { cout << "Channel Down" << endl; }
	void subtitleKorean()
	{
		cout << "Subtitle Korean" << endl;
		subtitle = Subtitle::KOREAN;
	}

	void subtitleEnglish()
	{
		cout << "Subtitle English" << endl;
		subtitle = Subtitle::ENGLISH;
	}

	void subtitleOff()
	{
		cout << "Subtitle Off" << endl;
		subtitle = Subtitle::OFF;
	}

	Subtitle getSubtitle() { return subtitle; }
private:
	Subtitle subtitle = Subtitle::OFF;
};

 

자막 관련 명령어는 이전 자막 상태를 알기 위해 변수가 하나 필요하다.

execute로 자막 상태를 변경할 때, 현재 상태를 저장하고 자막을 변경하면 된다.

그리고 취소 작업을 하게 되면 이전 자막 상태를 보고 다시 자막을 변경하도록 undo를 구현하였다.

class TelevisionSubtitleKorean : public Command
{
public:
	TelevisionSubtitleKorean(Television *tv) : television(tv) { }

	void execute() override
	{
		prevSubtitle = television->getSubtitle();
		television->subtitleKorean();
	}

	void undo() override
	{
		switch (prevSubtitle)
		{
		case Television::Subtitle::KOREAN:
			television->subtitleKorean();
			break;
		case Television::Subtitle::ENGLISH:
			television->subtitleEnglish();
			break;
		case Television::Subtitle::OFF:
			television->subtitleOff();
			break;
		default:
			break;
		}
	}
private:
	Television *television;
	Television::Subtitle prevSubtitle;
};

 

전체 코드는 다음과 같다.

#include <iostream>
#include <string>
#include <memory>
#include <vector>

using namespace std;

class Command
{
public:
	virtual ~Command() = default;
	virtual void execute() = 0;
	virtual void undo() = 0;
};

class EmptyCommand : public Command
{
public:
	void execute() override { cout << "Can't execute. This command is empty" << endl; }
	void undo() override { cout << "Can't undo. This command is empty" << endl; }
};

/* ------------------- Light ------------------- */
class Light
{
public:
	void on() { cout << "Light is On" << endl; }
	void off() { cout << "Light is off" << endl; }
};

class LightOnCommand : public Command
{
public:
	LightOnCommand(Light *l) : light(l) { }
	void execute() override { light->on(); }
	void undo() override { light->off(); }
private:
	Light *light;
};

class LightOffCommand : public Command
{
public:
	LightOffCommand(Light *l) : light(l) { }
	void execute() override { light->off(); }
	void undo() override { light->on(); }
private:
	Light *light;
};
/* --------------------------------------------- */

/* ---------------- Television ---------------- */
class Television
{
public:
	enum class Subtitle { OFF, KOREAN, ENGLISH };
	void channelUp() { cout << "Channel Up" << endl; }
	void channelDown() { cout << "Channel Down" << endl; }
	void subtitleKorean()
	{
		cout << "Subtitle Korean" << endl;
		subtitle = Subtitle::KOREAN;
	}

	void subtitleEnglish()
	{
		cout << "Subtitle English" << endl;
		subtitle = Subtitle::ENGLISH;
	}

	void subtitleOff()
	{
		cout << "Subtitle Off" << endl;
		subtitle = Subtitle::OFF;
	}

	Subtitle getSubtitle() { return subtitle; }
private:
	Subtitle subtitle = Subtitle::OFF;
};

class TelevisionChannelUp : public Command
{
public:
	TelevisionChannelUp(Television *tv) : television(tv) { }
	void execute() override { television->channelUp(); }
	void undo() override { television->channelDown(); }
private:
	Television *television;
};

class TelevisionChannelDown : public Command
{
public:
	TelevisionChannelDown(Television *tv) : television(tv) { }
	void execute() override { television->channelDown(); }
	void undo() override { television->channelUp(); }
private:
	Television *television;
};

class TelevisionSubtitleKorean : public Command
{
public:
	TelevisionSubtitleKorean(Television *tv) : television(tv) { }

	void execute() override
	{
		prevSubtitle = television->getSubtitle();
		television->subtitleKorean();
	}

	void undo() override
	{
		switch (prevSubtitle)
		{
		case Television::Subtitle::KOREAN:
			television->subtitleKorean();
			break;
		case Television::Subtitle::ENGLISH:
			television->subtitleEnglish();
			break;
		case Television::Subtitle::OFF:
			television->subtitleOff();
			break;
		default:
			break;
		}
	}
private:
	Television *television;
	Television::Subtitle prevSubtitle;
};

class TelevisionSubtitleEnglish : public Command
{
public:
	TelevisionSubtitleEnglish(Television *tv) : television(tv) { }

	void execute() override
	{
		prevSubtitle = television->getSubtitle();
		television->subtitleEnglish();
	}

	void undo() override
	{
		switch (prevSubtitle)
		{
		case Television::Subtitle::KOREAN:
			television->subtitleKorean();
			break;
		case Television::Subtitle::ENGLISH:
			television->subtitleEnglish();
			break;
		case Television::Subtitle::OFF:
			television->subtitleOff();
			break;
		default:
			break;
		}
	}
private:
	Television *television;
	Television::Subtitle prevSubtitle;
};

class TelevisionSubtitleOff : public Command
{
public:
	TelevisionSubtitleOff(Television *tv) : television(tv) { }

	void execute() override
	{
		prevSubtitle = television->getSubtitle();
		television->subtitleOff();
	}

	void undo() override
	{
		switch (prevSubtitle)
		{
		case Television::Subtitle::KOREAN:
			television->subtitleKorean();
			break;
		case Television::Subtitle::ENGLISH:
			television->subtitleEnglish();
			break;
		case Television::Subtitle::OFF:
			television->subtitleOff();
			break;
		default:
			break;
		}
	}
private:
	Television *television;
	Television::Subtitle prevSubtitle;
};

/* -------------------------------------------- */

class RemoteControl
{
public:
	RemoteControl() = default;
	void setCommand(Command *upCommand, Command *downCommand, int num)
	{
		upCommands[num] = upCommand;
		downCommands[num] = downCommand;
	}

	void pressUpButton(int num)
	{
		if (upCommands[num] != nullptr)
		{
			upCommands[num]->execute();
			undoCommand = upCommands[num];
		}
	}

	void pressDownButton(int num)
	{
		if (downCommands[num] != nullptr)
		{
			downCommands[num]->execute();
			undoCommand = downCommands[num];
		}
	}

	void pressUndoButton() { undoCommand->undo(); }
private:
	constexpr static int COMMAND_SIZE = 5;
	shared_ptr<EmptyCommand> empty = make_shared<EmptyCommand>();
	vector<Command *> upCommands = vector<Command *>(COMMAND_SIZE, empty.get());
	vector<Command *> downCommands = vector<Command *>(COMMAND_SIZE, empty.get());
	Command *undoCommand = empty.get();
};

int main()
{
	RemoteControl* remote = new RemoteControl(); // Invoker

	Light* light = new Light(); // Receiver
	LightOnCommand* lightOn = new LightOnCommand(light); // ConcreteCommand 
	LightOffCommand* lightOff = new LightOffCommand(light); 

	Television* television = new Television();
	TelevisionChannelUp* tvChannelUp = new TelevisionChannelUp(television);
	TelevisionChannelDown* tvChannelDown = new TelevisionChannelDown(television);
	TelevisionSubtitleKorean* tvSubtitleKorean = new TelevisionSubtitleKorean(television);
	TelevisionSubtitleEnglish* tvSubtitleEnglish = new TelevisionSubtitleEnglish(television);
	TelevisionSubtitleOff* tvSubtitleOff = new TelevisionSubtitleOff(television);

	remote->setCommand(lightOn, lightOff, 0);
	remote->setCommand(tvChannelUp, tvChannelDown, 1);
	remote->setCommand(tvSubtitleKorean, tvSubtitleOff, 2);
	remote->setCommand(tvSubtitleEnglish, tvSubtitleOff, 3);

	cout << "UNDO TEST" << endl;
	for (int i = 0; i < 5; i++)
	{
		cout << i << "> up   / on  command : "; remote->pressUpButton(i);
		cout << i << "> undo               : "; remote->pressUndoButton();
		cout << i << "> down / off command : "; remote->pressDownButton(i);
		cout << i << "> undo               : "; remote->pressUndoButton();
	}

	cout << endl;
	cout << "UNDO SUBTITLE TEST" << endl;
	remote->pressUpButton(2);
	remote->pressUpButton(3);
	remote->pressUndoButton();

	return 0;
}

 

한글 자막 → 영어 자막으로 변경 후, 취소 버튼을 누르면 한글 자막이 된다.


넷플릭스 루틴 만들기

 

커맨드 패턴에서 생성된 패턴을 이용해 새로운 패턴을 조합할 수 있다.

 

예를 들어 어떤 사용자는 넷플릭스를 볼 때, 항상 영어 자막을 보고 불을 끈 상태로 시청한다면,

해당 명령을 모아서 "넷플릭스 모드 On" 명령을 만들 수 있다.

 

먼저 여러 명령을 하나로 모으는 RoutineCommand를 만들자.

class RoutineCommand : public Command
{
public:
	RoutineCommand(const vector<Command*> &c) : commands(c) { }

	void execute() override
	{
		for (const auto &command : commands)
			command->execute();
	};

	void undo() override
	{
		for (int i = commands.size() - 1; i >= 0; --i)
			commands[i]->undo();
	}
private:
	vector<Command*> commands;
};

 

넷플릭스 모드 On 명령은 영어 자막을 사용하고 불을 끈다.

넷플릭스 모드 Off반대 명령어를 넣어서 만들면 된다.

	vector<Command*> netflixModeOn = { tvSubtitleEnglish, lightOff };
	vector<Command*> netflixModeOff = { tvSubtitleOff, lightOn };

	RoutineCommand* netflixModeOnCommand = new RoutineCommand(netflixModeOn);
	RoutineCommand* netflixModeOffCommand = new RoutineCommand(netflixModeOff);

 

만들어진 RoutineCommandCommand Interface를 구현하였기 때문에 리모컨에 할당할 수 있다.

remote->setCommand(netflixModeOnCommand, netflixModeOffCommand, 4);

 

전체 코드는 다음과 같다.

#include <iostream>
#include <string>
#include <memory>
#include <vector>

using namespace std;

class Command
{
public:
	virtual ~Command() = default;
	virtual void execute() = 0;
	virtual void undo() = 0;
};

class EmptyCommand : public Command
{
public:
	void execute() override { cout << "Can't execute. This command is empty" << endl; }
	void undo() override { cout << "Can't undo. This command is empty" << endl; }
};

/* ------------------- Light ------------------- */
class Light
{
public:
	void on() { cout << "Light is On" << endl; }
	void off() { cout << "Light is off" << endl; }
};

class LightOnCommand : public Command
{
public:
	LightOnCommand(Light *l) : light(l) { }
	void execute() override { light->on(); }
	void undo() override { light->off(); }
private:
	Light *light;
};

class LightOffCommand : public Command
{
public:
	LightOffCommand(Light *l) : light(l) { }
	void execute() override { light->off(); }
	void undo() override { light->on(); }
private:
	Light *light;
};
/* --------------------------------------------- */

/* ---------------- Television ---------------- */
class Television
{
public:
	enum class Subtitle { OFF, KOREAN, ENGLISH };
	void channelUp() { cout << "Channel Up" << endl; }
	void channelDown() { cout << "Channel Down" << endl; }
	void subtitleKorean()
	{
		cout << "Subtitle Korean" << endl;
		subtitle = Subtitle::KOREAN;
	}

	void subtitleEnglish()
	{
		cout << "Subtitle English" << endl;
		subtitle = Subtitle::ENGLISH;
	}

	void subtitleOff()
	{
		cout << "Subtitle Off" << endl;
		subtitle = Subtitle::OFF;
	}

	Subtitle getSubtitle() { return subtitle; }
private:
	Subtitle subtitle = Subtitle::OFF;
};

class TelevisionChannelUp : public Command
{
public:
	TelevisionChannelUp(Television *tv) : television(tv) { }
	void execute() override { television->channelUp(); }
	void undo() override { television->channelDown(); }
private:
	Television *television;
};

class TelevisionChannelDown : public Command
{
public:
	TelevisionChannelDown(Television *tv) : television(tv) { }
	void execute() override { television->channelDown(); }
	void undo() override { television->channelUp(); }
private:
	Television *television;
};

class TelevisionSubtitleKorean : public Command
{
public:
	TelevisionSubtitleKorean(Television *tv) : television(tv) { }

	void execute() override
	{
		prevSubtitle = television->getSubtitle();
		television->subtitleKorean();
	}

	void undo() override
	{
		switch (prevSubtitle)
		{
		case Television::Subtitle::KOREAN:
			television->subtitleKorean();
			break;
		case Television::Subtitle::ENGLISH:
			television->subtitleEnglish();
			break;
		case Television::Subtitle::OFF:
			television->subtitleOff();
			break;
		default:
			break;
		}
	}
private:
	Television *television;
	Television::Subtitle prevSubtitle;
};

class TelevisionSubtitleEnglish : public Command
{
public:
	TelevisionSubtitleEnglish(Television *tv) : television(tv) { }

	void execute() override
	{
		prevSubtitle = television->getSubtitle();
		television->subtitleEnglish();
	}

	void undo() override
	{
		switch (prevSubtitle)
		{
		case Television::Subtitle::KOREAN:
			television->subtitleKorean();
			break;
		case Television::Subtitle::ENGLISH:
			television->subtitleEnglish();
			break;
		case Television::Subtitle::OFF:
			television->subtitleOff();
			break;
		default:
			break;
		}
	}
private:
	Television *television;
	Television::Subtitle prevSubtitle;
};

class TelevisionSubtitleOff : public Command
{
public:
	TelevisionSubtitleOff(Television *tv) : television(tv) { }

	void execute() override
	{
		prevSubtitle = television->getSubtitle();
		television->subtitleOff();
	}

	void undo() override
	{
		switch (prevSubtitle)
		{
		case Television::Subtitle::KOREAN:
			television->subtitleKorean();
			break;
		case Television::Subtitle::ENGLISH:
			television->subtitleEnglish();
			break;
		case Television::Subtitle::OFF:
			television->subtitleOff();
			break;
		default:
			break;
		}
	}
private:
	Television *television;
	Television::Subtitle prevSubtitle;
};

/* -------------------------------------------- */

class RemoteControl
{
public:
	RemoteControl() = default;
	void setCommand(Command *upCommand, Command *downCommand, int num)
	{
		upCommands[num] = upCommand;
		downCommands[num] = downCommand;
	}

	void pressUpButton(int num)
	{
		if (upCommands[num] != nullptr)
		{
			upCommands[num]->execute();
			undoCommand = upCommands[num];
		}
	}

	void pressDownButton(int num)
	{
		if (downCommands[num] != nullptr)
		{
			downCommands[num]->execute();
			undoCommand = downCommands[num];
		}
	}

	void pressUndoButton() { undoCommand->undo(); }
private:
	constexpr static int COMMAND_SIZE = 5;
	shared_ptr<EmptyCommand> empty = make_shared<EmptyCommand>();
	vector<Command *> upCommands = vector<Command *>(COMMAND_SIZE, empty.get());
	vector<Command *> downCommands = vector<Command *>(COMMAND_SIZE, empty.get());
	Command *undoCommand = empty.get();
};

class RoutineCommand : public Command
{
public:
	RoutineCommand(const vector<Command*> &c) : commands(c) { }

	void execute() override
	{
		for (const auto &command : commands)
			command->execute();
	};

	void undo() override
	{
		for (int i = commands.size() - 1; i >= 0; --i)
			commands[i]->undo();
	}
private:
	vector<Command*> commands;
};

int main()
{
	RemoteControl* remote = new RemoteControl(); // Invoker

	Light* light = new Light(); // Receiver
	LightOnCommand* lightOn = new LightOnCommand(light); // ConcreteCommand
	LightOffCommand* lightOff = new LightOffCommand(light);

	Television* television = new Television();
	//TelevisionChannelUp* tvChannelUp = new TelevisionChannelUp(television);
	//TelevisionChannelDown* tvChannelDown = new TelevisionChannelDown(television);
	//TelevisionSubtitleKorean* tvSubtitleKorean = new TelevisionSubtitleKorean(television);
	TelevisionSubtitleEnglish* tvSubtitleEnglish = new TelevisionSubtitleEnglish(television);
	TelevisionSubtitleOff* tvSubtitleOff = new TelevisionSubtitleOff(television);

	vector<Command*> netflixModeOn = { tvSubtitleEnglish, lightOff };
	vector<Command*> netflixModeOff = { tvSubtitleOff, lightOn };

	RoutineCommand* netflixModeOnCommand = new RoutineCommand(netflixModeOn);
	RoutineCommand* netflixModeOffCommand = new RoutineCommand(netflixModeOff);

	remote->setCommand(netflixModeOnCommand, netflixModeOffCommand, 4);

	cout << "Netflix Mode On" << endl;
	remote->pressUpButton(4);
	cout << "\nNetflix Mode Off" << endl;
	remote->pressDownButton(4);
	cout << "\nNetflix Mode Undo" << endl;
	remote->pressUndoButton();

	return 0;
}


ClassDiagram.md

```mermaid
  classDiagram    
    class Command { 
      execute()*
      undo()*     
    }
    class ConcreteCommand { 
      execute()
      undo()
    }
    class Receiver {
      action()
    }
    class Client {

    }
    class Invoker {
      setCommand()
    }

    <<Interface>> Command  
    Command <|.. ConcreteCommand
    Receiver <-- ConcreteCommand  
    Client --> Receiver
    Client --> ConcreteCommand
    Command <-- Invoker
   
    note for ConcreteCommand "execute() => receiver->action()"
```
반응형

댓글