[디자인 패턴] Strategy 패턴 (알고리즘 모두 바꾸기)

트레드링스

참고: JAVA 언어로 배우는 디자인 패턴 입문


안녕하세요^^ 물류플랫폼 트레드링스 개발팀 양갱 입니다.

오늘 공부할 디자인 패턴은 Strategy 패턴 입니다.

Strategy 는 우리말로 '전략'이라는 뜻입니다. 


세상살면서 참 전략을 세워야 하는 일들이 많은데요.. 고단한 우리네 인생..

저는 Strategy라는 말을 듣고 떠오르는 것이 하나 있었습니다.

그것은 바로! 두구두구두구 (입으로 내는 효과음)

상남자들의 스포츠 미식축구 입니다.

'그냥 치고박고 싸우는거 아냐?' 라고 하시는 분들도 계실텐데요.

미식축구는 상황에 맞게 수많은 전략을 짜고 

그것을 성공시키려는 쪽과 막으려는 쪽이 싸우는? 스포츠 입니다.




오늘 우리가 공부할 Strategy 패턴도 이런 전략들을
상황에 맞춰 적절하게 바꿔가며 쓸 수 있게 해주는 패턴입니다.

Strategy 패턴을 사용한 오늘의 예제는 '가위, 바위, 보' 게임입니다.

여기서 가위, 바위, 보 게임의 전략은 두가지 방법으로 생각해 봤습니다.

'이기면 다음판에도 같은 것을 낸다.'  (Winning Strategy)
'직전에 냈던 손에서 다음 낼 손을 확률적으로 계산한다.'  (ProbStrategy)

전체적인 구조입니다.


Hand 클래스는 가위바위보의 '손'을 표시하는 클래스 입니다.
클래스 내부에서 주먹은 0, 가위는 1, 보는 2 라고 정했습니다.

이 클래스 내부에서 실제로 손의 승패를 판정하는 것은 fight라는 메소드입니다.
승패의 판정에서는 손의 값을 사용하고 있습니다.

이 식은 복잡해 보이지만, 식이 탄생한 이유를 알고 보면 쉽습니다.
this의 손의 값에 1을 더한것이  h의 손의 값!

즉, this가 주먹이라면 h는 가위, this가 가위하면 h는 보, this가 보라면 h는 주먹이 되므로 this는 h를 이긴다는 것을 의미합니다.

나머지 연산자 %를 사용해서 3의 나머지를 구하는 것은 보(2)에 1을 더했을때 주먹(0)이 나오도록 하기 위해서 입니다.


내가 가진 전략이 100개면 이 전략을 추상적으로 묶어서 관리할 Strategy 인터페이스 입니다.

여기에서 인터페이스는 추상적인 메소드들을 가지고 있는데요.
인터페이스를 구현할 구체적인 클래스들(=전략들)이 구현할 메소드들 입니다.

뭔 소리야? 알아듣게 말해!

nextHand()'다음에 내는 손을 얻기' 위한 메소드 입니다.
즉, 이 메소드가 호출되면 Strategy 인터페이스를 구현하는 클래스(= 구체적인 전략들)를 사용해서
다음에 낼 손을 결정하는 것입니다.

Study는 '직전에 낸 손으로 이겼는지, 졌는지'를 학습하기 위한 메소드입니다.
직전의 nextHand 메소드를 호출해서 이겼을 경우 study(true)로 호출하고, 진 경우 study(false)호출합니다.  

인터페이스로 선언했기 때문에 구체적인 전략들을 바꿔가며 가위바위보를 이길 수 있습니다.

WinningStrategy 클래스는 Strategy 인터페이스를 구현하는 클래스 즉, 전략중에 하나입니다.
'인터페이스를 구현했다' 라는 말은 nextHand()와 study()라는 두 개의 메소드를 구현하는 것입니다.

이 전략은 직전 승부에서 이겼으면 다음에도 같은 손을 낸다는 바보같은 전략입니다.
만일 직전 승부에서 졌다면 다음손은 난수를 사용해 결정합니다.


prevHand 필드는 이전 승부에서 내밀었던 손을 저장합니다.

ProbStrategy 클래스는 또 하나의 구체적인 '전략' 입니다.
이 전략은 다음 손은 언제나 난수로 결정하지만, 과거 승패의 이력을 사용해서 각각의 손을 낼 확률을 바꾸고 있습니다.

history 필드는 과거의 승패를 반영한 확률계산을 위한 표를 만듭니다.
history는 int의 2차원 배열로 각 차원의 첨자는 다음과 같은 의미를 가집니다.

 이 식의 값이 크면 클수록 과거의 확률이 높다는 의미 입니다.

...history[0][0] : 주먹, 주먹을 자신이 냈을 때의 과거의 승수
...history[0][1] : 주먹, 가위를 자신이 냈을 때의 과거의 승수
...history[0][2] : 주먹, 보를 자신이 냈을 때의 과거의 승수

이전에 자신이 주먹을 냈다고 가정해 볼게요. 이때 다음에 자신이 무엇을 낼것인지 위에 적은
history[0][0], history[0][1], history[0][2]의 값에서 확률을 계산하는 것입니다.

요약하면, 이 세가지 식의 값을 더해서(getSum() 메소드) 0부터 그 수까지의 난수를 계산하고 그것을 기초로 다음 손을 결정합니다.

예를 들어,
...history[0][0] 의 값이 3
...history[0][1] 의 값이 5
...history[0][2] 의 값이 7
이라고 가정하겠습니다.

이 경우 주먹, 가위, 보를 낼 비율을 3:5:7로 해서 다음의 손을 결정합니다.
0 이상 15(=3+5+7) 미만의 난수값을 얻어서

... 0 이상 3 미만이면 주먹
... 3 이상 8(=3+5) 미만이면 가위
... 8 이상 15(=3+5+7) 미만이면 보

라고 합니다.

그냥 가위바위보 지고 만다..

Player 클래스는 가위바위보를 하는 사람을 표현한 클래스입니다.
nextHand 메소드는 다음에 낼 손을 얻기 위한 것이지만, 실제로 다음의 손을 결정하는 것은 내가 선택한 구체적인 전략입니다.

Main 클래스는 실제로 컴퓨터에서 가위바위보를 실행하기 위한 클래스입니다.
여기서는 두명의 플레이어를 대전시키고 있습니다.


실행결과:


Strategy 패턴에서는 알고리즘의 부분을 다른 부분과 분리해서 알고리즘의 인터페이스(API) 부분만 정의하고 있습니다. 그리고 프로그램에서 위임을 통해 알고리즘(=전략)을 이용합니다.

이것은 프로그램을 더 복잡하게 만드는 것처럼 보이지만 실제로는 그렇지 않습니다.

예를들어, 새로운 알고리즘을 추가하거나 변경하고 싶다고 한다면 Strategy 패턴을 사용함으로써Strategy 역할의 인터페이스(API)는 변경하지 않고 구체적인 전략의 역할만 추가하거나 수정하면 끝입니다.


이것이 가능한 것은 '위임'이라는 느슨한 연결을 사용하고 있기 때문에 알고리즘을 쉽게 교환할 수 있는 것입니다.

이것이 Strategy 패턴의 핵심입니다.
여러분 구체적인 전략을 먼저 이해하려 하지 마시고, 전체적인 구조인 숲을 보는 것에 집중하시기 바랍니다.

기업문화 엿볼 때, 더팀스

로그인

/