개발새발

Command Pattern ( 커멘드 패턴) 본문

우아한테크코스/level1

Command Pattern ( 커멘드 패턴)

무비인 2022. 4. 3. 23:36

학습 동기

아래와 같이, 명령어에 따라 다른 행위를 구현할 때가 있다. 

이런 코드는 명령어가 많아지면 조건문이 늘어나고, 가독성도 떨어진다. 

또한 작업 요청과 처리를 한 클래스 내에서 해야한다. 이렇게 하면 객체에 과한 책임을 지게 하는게 아닌가?

(과거 카페에서 주문 받고 제조까지 혼자 다 한 경험이 있었는데,, 무지 힘들었다)

 

이 상황에 커멘드 패턴을 적용해보자!

 

커멘드 패턴 적용 전, 조건 분기가 많은 코드 모습

 

커멘드 패턴이란?

작업 요청 처리를 분리하는 방법 중 하나이다. 

아래 예시를 보면 연결해줘 라는 작업 요청을 받고, 어떤 기계에 연결할지는 처리 담당에서 결정한다. 

위에 체스 미션 요청 처리를 커멘드 패턴으로 변경해보자. 

 

 

커멘드 패턴에서 사용되는 개념

 클라이언트 (Client) : 명령어를 입력받음

커멘드 (Command) : 명령에 따른 행위가 들어있음 

발동자 (Invoker) :  명령을 수신자에게 전달함

수신자 (Receiver) : 행위를 전달받아 실행함

 


체스 게임 코드로 보는 커멘드 패턴


 

 클라이언트 (ConsoleApplication)

Play 메서드 내에서 inputMenu() 로 명령어를 입력받는다.

현재 로직에서는 1)start 2)staus 3)end 4)move 라는 명령어가 있고, 

move 일 경우 현재 위치와 이동위치를 함께 입력받아야한다.

 

 


 

② 커멘드 (Menu) : 명령에 따른 행위가 들어있다.

보통 행위라고 하면 메서드를 떠올릴 것이다. 

커멘드 패턴에서는 행위를 공통 인터페이스를 상속받은 클래스로 구현한다. 

 

Menu는 명령어를 구현하기 위한 공통 인터페이스이다.

Command 명령어 = new Start();

명령어 라는 타입을 두고, 타입에 구현체를 start나, move, end 로 지정하고 싶지 않은가?

공통 인터페이스를 두고, 이 인터페이스를 상속받아 구현체를 구현한다면 상위 타입에 하위 타입을 넣는 것이 가능해진다.

 

아래 예시에서는  Play 라는 행위를 지정한다.(커멘드 패턴에서 보통 execute 라는 메서드명을 사용한다.. )

 

 

Start 명령어는  보드를 초기화 한다.

인터페이스에 명시 되어있는 play 라는 메서드를 오버라이딩 하여 보드를 초기화하는 로직을 구현한다. 

 

 

Move 명령어는 움직일 체스 말의 위치와 이동할 위치를 입력받고, 유효한 위치이면 이동시킨다.

인터페이스에 명시 되어있는 play 라는 메서드를 오버라이딩 하여 보드 위에 말을 이동하는 로직을 구현한다. 

 

 

Status 명령어는 현재 경기 점수와 승패 여부를 출력한다.

인터페이스에 명시 되어있는 play 라는 메서드를 오버라이딩 하여 보드 위에 말들의 점수를 합산하여 현재 이기고 있는 팀을

판별 하고, 결과를 출력하는 로직을 출력한다.

 

 

End 명령어는 게임을 종료시킨다.

인터페이스에 명시 되어있는 play 라는 메서드를 오버라이딩 하여 게임을 종료시킨다.

 

 


 

③ 발동자 (MenuType) :  명령을 수신자에게 전달한다.

명령어를 start, move, status, end 로만 제한하기 위하여 MenuType 을 enum 클래스로 만들었다.

enum value 로 Function<String[], Menu> 를 사용했는데, 입력받은 명령어에서

move 에 현재 위치와 도착 위치를 함께 보내줘야 하기 때문에 Function 을 사용했다. (추후 보충 설명 추가)

 

 

입력받은 명령어에 따라 해당 명령어를 구현한 클래스를 반환한다.

스트림을 사용하여 입력받은 명령어와 enum 타입이 일치하면 해당 객체를 반환한도록 되어있다. 

equalsIgnoreCase() 를 사용한 이유는 , start 나 Start , START 와 같이 대소문자에 상관 없이 내용 값만 비교하고 싶었다.

apply 를 하게되면 Function 이 실행되면서 매개변수로 받은 입력 데이터를 넘겨준다. 

 

스트림을 사용하며 명령어와 일치하면, 해당 명령어를

 


 

 수신자 (ChessController) : 행위를 전달받아 실행한다.

커멘드 구현체들을 보면, chessContorller.메서드() 를 호출하고 있다.

chessContorller은 수신자로 행위를 실행하는 주체를 의미한다. 

 

 


느낀점

처음 이 코드를 작성했을 당시에는 커멘드 패턴을 몰랐다. 단순히 조건 분기문을 제거하기 위해 공통 인터페이스를 만들고,

인터페이스를 구현한 행위 구현체들을 만들었다. 이후에 코드리뷰를 하며 이런 구조가 커멘드 패턴임을 알게되었다. 

 

커멘드 패턴을 사용해 명령 요청과 처리를 분리하여 코드를 작성하니 명령 요청을 받는 주체는 행위에 대해 몰라도 되고, 

처리를 담당하는 곳에서 행위를 지정할 수 있어서, 명령어가 추가되면, 명령어의 구현체와 발동자(MenuType) 등 최소한의 

변경으로 유연한 대처가 가능해짐을 느꼈다. 

 

아직 많은 디자인 패턴을 아는 것이 아니라, 추후 여러 패턴을 공부해보고 장 단점을 비교한 내용을 추가해봐도 좋겠다.

 

혹시나, 궁금한 분들이 있으실까봐 위의 코드를 구현한 전체 코드를 첨부한다.

 

참고 자료 

 

커맨드 패턴 - command pattern 이란 무엇인가?

메소드 호출을 캡슐화 하여 계산의 각 과정부분들을 결정화 시킬 수 있다. 캡슐화된 method 호출을 로그 기록용으로 저장하거나 취소 기능을 구현하고 재사용 할 수 있다. 이 기능을 구현하면서

blog.yevgnenll.me

-

'우아한테크코스 > level1' 카테고리의 다른 글

방어적 복사vs Unmodifiable vs copyOf 의 차이점  (1) 2022.03.21
List.copyOf 란?  (0) 2022.03.21
unmodifiableList 란?  (0) 2022.03.21
방어적 복사란?  (0) 2022.03.21
[레벨1] 블랙잭 회고  (2) 2022.03.21