JUINTINATION
커맨드(Command) 패턴 본문
반응형
커맨드 패턴이란?
실행될 기능을 캡슐화하여 기능의 실행을 요구하는 호출자 클래스(Invoker)와 실제 기능을 실행하는 수신자 클래스(Receiver) 사이의 의존성을 제거하여 이벤트가 발생했을 때 실행될 기능이 다양하면서 변경이 필요한 경우 실행될 기능의 변경에도 호출자 클래스의 수정없이 그대로 사용할 수 있도록 해주는 패턴이다.
다음과 같은 램프를 켜는 버튼이 있다고 하자.
class Lamp {
public void turnOn() {
System.out.println("Lamp.turnOn");
}
}
class Button {
private Lamp lamp;
public Button(Lamp lamp) {
this.lamp = lamp;
}
public void pressed() {
lamp.turnOn();
}
}
- 위 코드의 문제점
- 버튼이 눌렸을 때 램프를 켜는 대신에 다른 기능을 수행하기 위해서 코드를 수정해야 한다.
- 유지 보수의 어려움
- OCP 위반
- 버튼이 눌렸을 때 램프를 켜는 대신에 다른 기능을 수행하기 위해서 코드를 수정해야 한다.
예를 들어 알람 기능을 추가하게 되면 다음과 같이 코드를 수정해야 한다.
enum Mode { LAMP, ALARM }
class Lamp {
public void turnOn() {
System.out.println("Lamp.turnOn");
}
}
class Alarm {
public void start() {
System.out.println("Alarm.start");
}
}
class Button {
private Lamp lamp;
private Alarm alarm;
private Mode mode;
public Button(Lamp lamp, Alarm alarm) {
this.lamp = lamp;
this.alarm = alarm;
}
public void setMode(Mode mode) {
this.mode = mode;
}
public void pressed() {
switch (mode) {
case LAMP:
lamp.turnOn();
break;
case ALARM:
alarm.start();
break;
}
}
}
기능을 변경하거나 새로운 기능을 추가할 때마다 Button 클래스를 수정해야 하므로 OCP를 위반한다.
이를 해결하기 위해 버튼이 눌렸을 때 수행될 기능을 캡슐화한다.
class Button {
private Command command;
public Button(Command command) {
setCommand(command);
}
public void setCommand(Command command) {
this.command = command;
}
public void pressed() {
command.execute();
}
}
interface Command {
void execute();
}
class Lamp {
public void turnOn() {
System.out.println("Lamp.turnOn");
}
}
class LampOnCommand implements Command {
private Lamp lamp;
public LampOnCommand(Lamp lamp) {
this.lamp = lamp;
}
@Override
public void execute() {
lamp.turnOn();
}
}
class Alarm {
public void start() {
System.out.println("Alarm.start");
}
}
class AlarmStartCommand implements Command {
private Alarm alarm;
public AlarmStartCommand(Alarm alarm) {
this.alarm = alarm;
}
@Override
public void execute() {
alarm.start();
}
}
커맨드 패턴 컬레보레이션
Invoker : 기능의 실행을 요청하는 호출자 클래스
Command : 실행될 기능에 대한 인터페이스
ConcreteCommand : 실제로 실행되는 기능을 구현한 Command의 하위 클래스
Receiver : ConcreteCommand의 기능 실행을 위해 사용되는 수신자 클래스
커맨드 패턴을 이용한 간단한 예제
- 게임기 설명 : 세 개의 버튼(각각 1개의 빨강, 초록, 파랑 버튼)을 사용하여 게임을 진행
- 최초에 빨강 버튼을 누르면 게임 1, 초록 버튼을 누르면 게임 2, 파랑 버튼을 누르면 게임 3을 실행한다. 오락기는 3개의 게임을 제공한다. 각 게임에서 버튼의 기능은 다음과 같다.
- 게임 1 : 주사위가 주사위고 또 주사위다(세 개의 주사위 중 가장 큰 값의 주사위를 고르는 게임. 세 개의 주사위가 생성되고, 숫자는 1~6 중에 랜덤으로 생성됨)
- 빨강 버튼: 왼쪽 주사위 선택. 가장 큰 값이 아니라면 게임 오버
- 초록 버튼: 가운데 주사위 선택. 가장 큰 값이 아니라면 게임 오버
- 파란 버튼: 오른쪽 주사위 선택. 가장 큰 값이 아니라면 게임 오버
- 게임 2 : 그쯤에서 재빨리 블록 빼내기(버튼 색과 동일한 색의 블록을 빼내는 게임. 블록의 개수는 10개이고, 색은 랜덤으로 생성됨)
- 빨강 버튼: 가장 밑이 빨강 블록이라면 제거, 빨강 블록이 아니라면 게임오버
- 초록 버튼: 가장 밑이 초록 블록이라면 제거, 초록 블록이 아니라면 게임오버
- 파랑 버튼: 가장 밑이 파랑 블록이라면 제거, 파랑 블록이 아니라면 게임오버
- 게임 3: 햄버거 가게의 당일치기 견습생(3개의 재료를 사용하여 햄버거를 만드는 게임. 햄버거의 내용물은 랜덤으로 생성됨)
- 빨강 버튼: 토마토 추가. 토마토를 추가할 위치가 아니라면 게임 오버
- 초록 버튼: 양상추 추가. 양상추를 추가할 위치가 아니라면 게임 오버
- 파랑 버튼: 고기 패티 추가. 고기 패티를 추가할 위치가 아니라면 게임 오버
- 게임 1 : 주사위가 주사위고 또 주사위다(세 개의 주사위 중 가장 큰 값의 주사위를 고르는 게임. 세 개의 주사위가 생성되고, 숫자는 1~6 중에 랜덤으로 생성됨)
- 구현 예시
[빨강버튼은 1을 입력, 초록버튼은 2를 입력, 파랑버튼은 3을 입력하세요]
게임을 선택하세요(게임1: 빨강버튼, 게임2: 초록버튼, 게임3: 파랑버튼)
2
게임2를 선택하였습니다.
블록과 동일한 색의 버튼을 누르는 게임입니다.
블록 순서: 빨강, 파랑, 초록, 초록, 빨강
버튼을 누르세요! 1
버튼을 누르세요! 3
버튼을 누르세요! 2
버튼을 누르세요! 1
게임 오버입니다. 게임을 종료합니다.
클래스 다이어그램
소스코드
import java.util.Set;
import java.util.HashSet;
import java.util.Random;
import java.util.Scanner;
public class Client {
public static void main(String[] args) {
Arcade.getInstance().run();
}
}
class Arcade {
private static Arcade arcade = new Arcade();
private String gameName;
private boolean isOvered;
private Arcade() {
this.isOvered = false;
}
public static Arcade getInstance() {
return arcade;
}
public void setGameName(String gameName) {
this.gameName = gameName;
}
public void setIsOvered(boolean isOvered) {
this.isOvered = isOvered;
}
public void run() {
Scanner sc = new Scanner(System.in);
boolean isFinished = false;
int buttonNum;
RedButton redButton = RedButton.getInstance();
GreenButton greenButton = GreenButton.getInstance();
BlueButton blueButton = BlueButton.getInstance();
DiceGame diceGame = new DiceGame(this);
BlockGame blockGame = new BlockGame(this);
HamburgerGame hamburgerGame = new HamburgerGame(this);
while (true) {
System.out.println("[빨강버튼은 1을 입력, 초록버튼은 2를 입력, 파랑버튼은 3을 입력하세요]\n");
System.out.println("게임을 선택하세요(게임1: 빨강버튼, 게임2: 초록버튼, 게임3: 파랑버튼)");
buttonNum = sc.nextInt();
switch (buttonNum) {
case 1:
System.out.println("게임1을 선택하였습니다.");
redButton.pressed();
break;
case 2:
System.out.println("게임2을 선택하였습니다.");
greenButton.pressed();
break;
case 3:
System.out.println("게임3을 선택하였습니다.");
blueButton.pressed();
break;
default:
System.out.println("게임을 종료합니다.");
isFinished = true;
}
if (isFinished) break;
switch (gameName) {
case "DiceGame":
redButton.setCommand(new LeftDiceCommand(diceGame));
greenButton.setCommand(new CenterDiceCommand(diceGame));
blueButton.setCommand(new RightDiceCommand(diceGame));
diceGame.start();
break;
case "BlockGame":
redButton.setCommand(new RedBlockCommand(blockGame));
greenButton.setCommand(new GreenBlockCommand(blockGame));
blueButton.setCommand(new BlueBlockCommand(blockGame));
blockGame.start();
break;
case "HamburgerGame":
redButton.setCommand(new TomatoCommand(hamburgerGame));
greenButton.setCommand(new CabbageCommand(hamburgerGame));
blueButton.setCommand(new PattyCommand(hamburgerGame));
hamburgerGame.start();
break;
}
if (gameName.equals("DiceGame")) {
buttonNum = sc.nextInt();
switch (buttonNum) {
case 1:
redButton.pressed();
break;
case 2:
greenButton.pressed();
break;
case 3:
blueButton.pressed();
break;
}
} else {
for (int i = 0; i < 5; i++) {
buttonNum = sc.nextInt();
switch (buttonNum) {
case 1:
redButton.pressed();
break;
case 2:
greenButton.pressed();
break;
case 3:
blueButton.pressed();
break;
}
if (isOvered) break;
}
}
setIsOvered(false);
redButton.setCommand(RedSelectCommand.getInstance());
greenButton.setCommand(GreenSelectedCommand.getInstance());
blueButton.setCommand(BlueSelectedCommand.getInstance());
}
sc.close();
}
}
class DiceGame {
private Arcade arcade;
private int leftDice, centerDice, rightDice, maxDice;
private Set<Integer> numSet = new HashSet<>();
public DiceGame(Arcade arcade) {
this.arcade = arcade;
}
public void start() {
System.out.println("세 개의 주사위 중 가장 큰 값의 주사위를 고르는 게임입니다.");
System.out.println("빨강 버튼: 왼쪽 주사위 선택 / 초록 버튼: 가운데 주사위 선택 / 파란 버튼: 오른쪽 주사위 선택");
Random random = new Random();
leftDice = 1 + random.nextInt(6);
numSet.add(leftDice);
do {
centerDice = 1 + random.nextInt(6);
} while (numSet.contains(centerDice));
numSet.add(centerDice);
do {
rightDice = 1 + random.nextInt(6);
} while (numSet.contains(rightDice));
numSet.add(rightDice);
System.out.println("왼쪽 주사위: " + leftDice + ", 가운데 주사위: " + centerDice + ", 오른쪽 주사위: " + rightDice);
maxDice = Math.max(Math.max(leftDice, centerDice), rightDice);
}
public void selectLeftDice() {
if (leftDice == maxDice) {
System.out.println("이겼습니다.");
} else {
System.out.println("게임 오버입니다. 게임을 종료합니다.");
}
}
public void selectCenterDice() {
if (centerDice == maxDice) {
System.out.println("이겼습니다.");
} else {
System.out.println("게임 오버입니다. 게임을 종료합니다.");
}
}
public void selectRightDice() {
if (rightDice == maxDice) {
System.out.println("이겼습니다.");
} else {
System.out.println("게임 오버입니다. 게임을 종료합니다.");
}
}
}
class BlockGame {
private Arcade arcade;
private String[] blocks;
private int blockNum;
private int cnt = 0;
public BlockGame(Arcade arcade) {
this.arcade = arcade;
}
public void start() {
System.out.println("버튼 색과 동일한 색의 블록을 빼내는 게임입니다.");
System.out.println("빨강 버튼: 가장 밑이 빨강 블록이라면 제거 / 초록 버튼: 가장 밑이 초록 블록이라면 제거 / 파란 버튼: 가장 밑이 파랑 블록이라면 제거");
blocks = new String[5];
Random random = new Random();
System.out.print("블록 순서: ");
for (int i = 0; i < 5; i++) {
blockNum = 1 + random.nextInt(3);
switch (blockNum) {
case 1:
blocks[i] = "빨강";
break;
case 2:
blocks[i] = "초록";
break;
case 3:
blocks[i] = "파랑";
break;
}
System.out.print(blocks[i] + ", ");
}
}
public void selectRedBlock() {
if (!blocks[cnt++].equals("빨강")) {
System.out.println("게임 오버입니다. 게임을 종료합니다.");
arcade.setIsOvered(true);
cnt = 0;
}
}
public void selectGreenBlock() {
if (!blocks[cnt++].equals("초록")) {
System.out.println("게임 오버입니다. 게임을 종료합니다.");
arcade.setIsOvered(true);
cnt = 0;
}
}
public void selectBlueBlock() {
if (!blocks[cnt++].equals("파랑")) {
System.out.println("게임 오버입니다. 게임을 종료합니다.");
arcade.setIsOvered(true);
cnt = 0;
}
}
}
class HamburgerGame {
private Arcade arcade;
private String[] ingredients;
private int ingredientNum;
private int cnt = 0;
public HamburgerGame(Arcade arcade) {
this.arcade = arcade;
}
public void start() {
System.out.println("3개의 재료를 사용하여 햄버거를 만드는 게임입니다.");
System.out.println("빨강 버튼: 토마토 추가 / 초록 버튼: 양배추 추가 / 파란 버튼: 고기 패티 추가");
ingredients = new String[5];
Random random = new Random();
System.out.print("블록 순서: ");
for (int i = 0; i < 5; i++) {
ingredientNum = 1 + random.nextInt(3);
switch (ingredientNum) {
case 1:
ingredients[i] = "토마토";
break;
case 2:
ingredients[i] = "양배추";
break;
case 3:
ingredients[i] = "고기 패티";
break;
}
System.out.print(ingredients[i] + ", ");
}
}
public void selectTomato() {
if (!ingredients[cnt++].equals("토마토")) {
System.out.println("게임 오버입니다. 게임을 종료합니다.");
arcade.setIsOvered(true);
cnt = 0;
}
}
public void selectCabbage() {
if (!ingredients[cnt++].equals("양배추")) {
System.out.println("게임 오버입니다. 게임을 종료합니다.");
arcade.setIsOvered(true);
cnt = 0;
}
}
public void selectPatty() {
if (!ingredients[cnt++].equals("고기 패티")) {
System.out.println("게임 오버입니다. 게임을 종료합니다.");
arcade.setIsOvered(true);
cnt = 0;
}
}
}
class RedButton {
private static RedButton redButton = new RedButton(RedSelectCommand.getInstance());
private RedButtonCommand theCommand;
private RedButton(RedButtonCommand theCommand) {
this.theCommand = theCommand;
}
public static RedButton getInstance() {
return redButton;
}
public void setCommand(RedButtonCommand newCommand) {
this.theCommand = newCommand;
}
public void pressed() {
this.theCommand.execute();
}
}
interface RedButtonCommand {
void execute();
}
class RedSelectCommand implements RedButtonCommand {
private static RedSelectCommand redSelectCommand = new RedSelectCommand();
private Arcade arcade = Arcade.getInstance();
private RedSelectCommand() {}
public static RedSelectCommand getInstance() {
return redSelectCommand;
}
@Override
public void execute() {
arcade.setGameName("DiceGame");
}
}
class LeftDiceCommand implements RedButtonCommand {
private DiceGame diceGame;
public LeftDiceCommand(DiceGame diceGame) {
this.diceGame = diceGame;
}
@Override
public void execute() {
diceGame.selectLeftDice();
}
}
class RedBlockCommand implements RedButtonCommand {
private BlockGame blockGame;
public RedBlockCommand(BlockGame blockGame) {
this.blockGame = blockGame;
}
@Override
public void execute() {
blockGame.selectRedBlock();
}
}
class TomatoCommand implements RedButtonCommand {
private HamburgerGame hamburgerGame;
public TomatoCommand(HamburgerGame hamburgerGame) {
this.hamburgerGame = hamburgerGame;
}
@Override
public void execute() {
hamburgerGame.selectTomato();
}
}
class GreenButton {
private static GreenButton greenButton = new GreenButton(GreenSelectedCommand.getInstance());
private GreenButtonCommand theCommand;
private GreenButton(GreenButtonCommand theCommand) {
this.theCommand = theCommand;
}
public static GreenButton getInstance() {
return greenButton;
}
public void setCommand(GreenButtonCommand newCommand) {
this.theCommand = newCommand;
}
public void pressed() {
this.theCommand.execute();
}
}
interface GreenButtonCommand {
void execute();
}
class GreenSelectedCommand implements GreenButtonCommand {
private static GreenSelectedCommand greenSelectedCommand = new GreenSelectedCommand();
private Arcade arcade = Arcade.getInstance();
private GreenSelectedCommand() {}
public static GreenSelectedCommand getInstance() {
return greenSelectedCommand;
}
@Override
public void execute() {
arcade.setGameName("BlockGame");
}
}
class CenterDiceCommand implements GreenButtonCommand {
private DiceGame diceGame;
public CenterDiceCommand(DiceGame diceGame) {
this.diceGame = diceGame;
}
@Override
public void execute() {
diceGame.selectCenterDice();
}
}
class GreenBlockCommand implements GreenButtonCommand {
private BlockGame blockGame;
public GreenBlockCommand(BlockGame blockGame) {
this.blockGame = blockGame;
}
@Override
public void execute() {
blockGame.selectGreenBlock();
}
}
class CabbageCommand implements GreenButtonCommand {
private HamburgerGame hamburgerGame;
public CabbageCommand(HamburgerGame hamburgerGame) {
this.hamburgerGame = hamburgerGame;
}
@Override
public void execute() {
hamburgerGame.selectCabbage();
}
}
class BlueButton {
private static BlueButton blueButton = new BlueButton(BlueSelectedCommand.getInstance());
private BlueButtonCommand theCommand;
private BlueButton(BlueButtonCommand theCommand) {
this.theCommand = theCommand;
}
public static BlueButton getInstance() {
return blueButton;
}
public void setCommand(BlueButtonCommand newCommand) {
this.theCommand = newCommand;
}
public void pressed() {
this.theCommand.execute();
}
}
interface BlueButtonCommand {
void execute();
}
class BlueSelectedCommand implements BlueButtonCommand {
private static BlueSelectedCommand blueSelectedCommand = new BlueSelectedCommand();
private Arcade arcade = Arcade.getInstance();
private BlueSelectedCommand() {}
public static BlueSelectedCommand getInstance() {
return blueSelectedCommand;
}
@Override
public void execute() {
arcade.setGameName("HamburgerGame");
}
}
class RightDiceCommand implements BlueButtonCommand {
private DiceGame diceGame;
public RightDiceCommand(DiceGame diceGame) {
this.diceGame = diceGame;
}
@Override
public void execute() {
diceGame.selectRightDice();
}
}
class BlueBlockCommand implements BlueButtonCommand {
private BlockGame blockGame;
public BlueBlockCommand(BlockGame blockGame) {
this.blockGame = blockGame;
}
@Override
public void execute() {
blockGame.selectBlueBlock();
}
}
class PattyCommand implements BlueButtonCommand {
private HamburgerGame hamburgerGame;
public PattyCommand(HamburgerGame hamburgerGame) {
this.hamburgerGame = hamburgerGame;
}
@Override
public void execute() {
hamburgerGame.selectPatty();
}
}
728x90
'JAVA객체지향디자인패턴' 카테고리의 다른 글
데커레이터(Decorator) 패턴 (0) | 2023.07.09 |
---|---|
옵서버(Observer) 패턴 (0) | 2023.07.06 |
스테이트(State) 패턴 (0) | 2023.07.06 |
스트래티지(Strategy) 패턴 (0) | 2023.07.06 |
싱글턴(Singleton) 패턴 (0) | 2023.07.05 |
Comments