강의) 네오 3단계 블랙잭 피드백(2/4)
블랙잭 시작과 상태 패턴에 강의2
📜 제목으로 보기
- 도메인이 쌓이고 난 후 이름 리팩토링
- 중요) 상태변화패턴이 시작되는 [최초 state가 나오기 전 상태]로서 Ready라는 이름으로 시작 -> 이것 역시 State의 카테고리인 상태객체로 바꿔보기
- 27) Ready도 스태틱 로직메서드를 위한 class가 아니라 State로 만들어보자
- 중요) Ready라는 시작 상태는 고유하며, 생성시 아무재료(정보)도 없이 시작되며 -> draw트리거메서드에 의해 바뀔 수 있다.
- my) 인페를 상카로 뽑아 카테고라이징 했다면, 구현체의 단독 상태, 메서드 개발은 자유롭다 -> 규칙에 맞는 메서드만 오버라이딩해서 개별구현하면 된다.
- 중요) 시작 State도 마찬가지로 다른 구현체(상태)로 넘어갈지 판단에 필요한 정보 cards를 상태값으로 가진다 -> 재료로 받진 않지만, 추가정보를 받아서 채우면서 판단 -> 상태값인 cards의 갯수에 따라 현재상태인지 -> 다른 상태로 넘어가야할지 판단할 수 있다.
- 28) 재료없이 생성되는 객체라도 -> 내부에서 빈 재료로 초기화하는 상태값을 가질 수 있다.
- 29) Ready도 draw(card)를 한다.
- 30) Ready가 가능한 상태를 생각해본다 -> 1장씩 받으니 Ready 그대로 or 2장 다받으면 Hit or Blackjack -> 업데이트된 상태값으로 isReady()로 물어봐야한다.
- 리팩토링과 함께
- 상태로서의 Ready를 Test하기
- 31) 상태로서의 Ready 테스트하기
- 32) 기존 테스트코드를 전체 리팩토링 ( Ready.start -> new Ready.draw().draw()로)
- 33) 개별 상태객체부터 시작하는 Test로 고치기(Ready시작 안해도 됨)
- 중요) 상태값을 가지는 객체라면, 그 상태값을 만드는 재료만 넣어주면, 그 정보를 가공하여 반영된 객체를 생성할 수 있다.
- 참고) 객체 생성시, 재료를 넘겨줄 때, 사용성을 고려하여 생성자 추가는 해도 괜찮다. -> new단일객체()도 귀찮은데 2개이상 생성하면서 List.of()로 묶어줘야한다? -> 클라이언트 배려한 가변인자 파라미터 빨간줄 생성
- 리팩1) 외부에서 인자전달시 List.of()로 묶어서 전달한다? -> 인자는 콤마로 + 파라미터는 가변으로 수정 by Ctrl+F6(change signature)
- 참고) 가변인자는 밖에서 없애줬떤 List.of를 그대로 쓰면 된다. stream대신 -> 내부 List.of()로 처리
- 34) 카드 2장으로 new Hit() 시작하여 hit -> hit 테스트
- 리팩4 & 중요) 많이 쓰이는 Test속 일급재료인 상수기반 단일객체는 Fixtures클래스에 모아두고 static import해서 단일객체가 객체지만, 상수처럼 갖다쓰자.
- 중요) Test에서 자주쓰는 단일객체(일급의 재료)를 Fixures클래스에서 상수로 선언하여 모아두자.-> new 단일() or 단일.of() / 단일.from() 등 인자에서 호출되는 단일객체 생성자(정펙매) 호출부분을 ctrl+alt+C로 객체를 상수추출한 뒤, F6 -> public(psf)으로 Fixtures 클래스로 옮겨주자.
- 참고) 테스트용 갖다쓰는 객체상수 클래스 Fixtures는 Add on-demand static import를 통해 편하게 현재클래스내 상수처럼 쓸 수 있게 한다.
- 참고) 객체 -> 상수 만들다가 기존 Fixtures에 뽑아 놓은 객체상수 발견시 -> ctrl+H로 일괄변경해주기. -> 의심되는 객체는 ctrl+shit+F로 Fixtures에 만들어놓은게 없나 살펴보기
도메인이 쌓이고 난 후 이름 리팩토링
Game(서비스같은)Test로 Game.메서드들 테스트 -> 구체적으로 클래스명 정하기
- 현재
Game.start()
이후 응답되는 상태 객체state
의.draw()
들만 테스트하고 있다.-
Game
에서 카드 2장받아start
를 하면 ->state
를 반환한다?
-
-
Game대신…
상태패턴의 state가 시작되기 전
을 의미하는Ready
의 준비를.start( 카드1,카드2)
받는 식은 어떨까?- Ready가 start하면 state를 응답해준다?
26) Game.start() -> Ready.start()로 이름 리팩토링
- 이름 리팩토링은
해당 파일로 가서 class명을 붙잡고 F2
->RenameTest도 같이 바꾸도록 select All
선택 후 Okay해줘야 바뀐다.- 클래스명 클릭 ->
F2
-> RenameTest 에서alt +a
후tab 2번 한 뒤 okay enter
- 클래스명 클릭 ->
중요) 상태변화패턴이 시작되는 [최초 state가 나오기 전 상태]로서 Ready라는 이름으로 시작 -> 이것 역시 State의 카테고리인 상태객체로 바꿔보기
스태틱 로직메서드
를 위한 class가 아니라 State
로 만들어보자
27) Ready도 상카(추상체) 구현을 시작으로 카테고라이징 된다.
-
현 class상태에서
State
를 구현하여 카테고라이징- impl해야하는 메소드를 가장 위로 올려, State임을 나타내주자
Hit or Black상태
임을 상태값으로 가지고 있다?
[잘못설계] Ready 상태는 start(card1, card2)를 활용하여 - Hit, bust 등 처럼,
이전 정보cards
+추가정보card
로 정보 업데이트후 ->업데이트된 정보로 객체 생성
을 위해상태값
형태로 cards를 가지고 있었다. -
Ready의 경우,
재료인 card1, card2
가 들어와서처음 초기화되어 시작
되며,cards정보를 통한 업데이트된 Ready
를 만들 일 없이 한번 만들어지고 끝이다?- 다른 State처럼
cards
를 받는게 아니다? - 그러면 State와 동일한 상태객체일까? 고민이 된다.
- 다른 State처럼
-
Ready는 어떻게 되었든
card1, card2를 재료
로 받아가공되어 내부에 가지는 상태값
을 가지게 될 것이다. -
기존 메서드를 보니 재료를 받았지만, 가공은 State로 될 가능성이 높다.
-
재료 가공을
State
로 해서 상태값으로 가지고 있자 -
복사해놓고 활용하면 더 쉬울 듯
-
생성자는 재료를 받아서
this.상태값=
을 초기화해줘야하는 의무를 가진다. 그 상태값은 메서드로 치면 State들이다.
-
-
문제점: 아직 state로 가기전의 상태인데, 초기화로 Hit 나 Blackjack상태를 가지게 된다. -> 메소드의 역할만 하고 있고 상태객체가 아니게 된다.
중요) Ready라는 시작 상태는 고유하며, 생성시 아무재료(정보)도 없이 시작되며 -> draw트리거메서드에 의해 바뀔 수 있다.
- Ready는 생성시 Card1, card1를 받는게 아니라 재료가 없는 빈 생성자로 시작해야한다.
- 1장 씩 draw하면서 아직 ready인지 물어보고
- 2장을 draw했을 때, hit or blackjack 상태로 바껴야한다. by draw메서드에 의해
구현체의 단독 상태, 메서드 개발은 자유롭다
-> 규칙에 맞는 메서드만 오버라이딩해서 개별구현하면 된다.
my) 인페를 상카로 뽑아 카테고라이징 했다면,
다른 구현체(상태)로 넘어갈지 판단에 필요한 정보
cards를 상태값으로 가진다 -> 재료로 받진 않지만, 추가정보를 받아서 채우면서 판단
-> 상태값인 cards
의 갯수에 따라 현재상태인지 -> 다른 상태로 넘어가야할지 판단할 수 있다.
중요) 시작 State도 마찬가지로 28) 재료없이 생성되는 객체라도 -> 내부에서 빈 재료로 초기화하는 상태값을 가질 수 있다.
일급
을 빈재료로 넣어야할 때 -> this.value = new 일급();
으로 일급생성자에 재료를 빼고 넣어주고 -> 일급으로 가서 [재료받는 것을 주생성자this]로 하여 -> 재료없이 내부 빈재료로 초기화하는 부생성자를 추가 생성
해줘야한다.
참고) -
Ready
의 재료없이 내부 빈재료로로 상태값을 만드는생성자
를 먼저 선언하고 ->상태값
도 만들어주자. -
이 때, 필요한 재료는 안받아오지만, 필요한
내부 빈 재료
가빈 일급컬렉션
이다… -
일급컬렉션에
재료받아 생성되는 주생성자
를 this로 활용하여 그 위에재료없는 부생성자
를 추가로 만들어주자.-
상태값 및 재료
가 원래 있던 놈들인데 없는 부생성자 만들시 ->Select None
으로 생성하면 된다. -
new ArraysList<>()
로 빈 재료를 넣어주면 된다.public final class Cards { private final List<Card> value; public Cards() { this(new ArrayList<>()); }
-
-
draw는 아직 처리안되어있으니
return null
로 State를 응답해주자.
29) Ready도 draw(card)를 한다.
일급상태값.add()
는 보이기에만 add시 기존 상태값+업데이트정보
로 -> 업데이트 상태값으로 새 일급
을 반환해주는 업데이트 일급 응답
함수다. 이 업데이트된 일급
으로 다음 상태
를 판단한다.
내부 -
업데이트된 상태값으로 새 일급 반환받기
@Override public State draw(final Card card) { //1. 다른 상태에서의 draw처럼 (1) 현재 정보들 + (2) 추가 정보를 가지고 내부업데이트해서 불변 새일급을 반환받는다. final Cards currentCards = this.cards.add(card); return null; }
-
업데이트된
상태값으로 -> 일급이니 메세지를 보내서 다음 객체
를 판단한다
업데이트된 상태값으로 isReady()
로 물어봐야한다.
30) Ready가 가능한 상태를 생각해본다 -> 1장씩 받으니 Ready 그대로 or 2장 다받으면 Hit or Blackjack -> -
아직 카드갯수가 2보다 작은 1개 상태의 ready상태라면,
업데이트 된 상태값으로 -> new 상태객체(상태값)
를 만들어 응답해주자.
늦게 생성됬어도 재료받는 생성자가 주생성자
라는 개념을 가지고 가자 -> 기존의 재료없는 생성자는 this를 활용해서 부생성자로서 재정의 해줘야한다.
참고) -
아직 재료없이 생성되는 Ready 생성자만 정의된 상태이니 생성자도 만들어줘야한다.
- **재료를 받게 되는 순간부터
늦게 생성됬어도 재료받는 생성자가 주생성자
라는 개념을 가지고 가자 **
- **재료를 받게 되는 순간부터
this()
내부로 복붙한 다음, 가공해서 주생성자의 파라미터에 맞춰주면 된다.
참고) 부생성자에서 주생성자 활용하여 정의: 기존 = 우항; 코드들을 복사하고 -
재료받은 주생성자가 생겼지만,
이놈은 외부에서는 호출안될 것 같으니 private
으로 처리해주고부생성자를 this로 다시 정의해주자
리팩토링과 함께
상태로서의 Ready를 Test하기
기존 테스트들이 Ready.start( card1, card2)로 많이 만들어졌기 때문에 일단은 살려둔 상태다.
31) 상태로서의 Ready 테스트하기
-
기존에 카드 2장을 받아서, 상태를 만들어주는 정적메서드의 주체자 Ready였다.
public class ReadyTest { @Test void hit() { // given & when : hit final State state = Ready.start(Card.of(Suit.SPADES, Denomination.TWO), Card.of(Suit.SPADES, Denomination.JACK)); // then assertThat(state).isInstanceOf(Hit.class); }
기존 코드보다 위에, 기존코드를 복붙해놓고 새 기준 코드를 작성한다
참고) 코드 수정이나 개발은 -
2장 받아 상태반환 메서드 ->
재료없이 최초 상태로서 출발하는 상태객체
의 코드로 변경하려면기존코드를 남겨두고, 위쪽에 복붙해서 수정
한다.
재료를 안받고 생성
되며, 상태 변환 트리거 메서드
를 호출하면서 다른 구현체 상태객체가 되는 조건까지 재료없이 내부 빈 재료로 초기화시켜 만든 상태값을 통해 확인
하면서 진행하면 된다.
중요) 최초 시작상태 객체는 -
Ready(Class)
로 시작하는.start()
메서드호출 대신직접 재료 없이 생성
하여 최초 상태가 시작된다.-
과정
-
.트리거메서드()
는 경우의 수 구현체를 다 받을 수 있는 추상체를 응답
하므로 -> 계속 체이닝으로 해서 호출이 가능
하다.
중요) 상태객체-
상태변화 트리거인
draw()
를 호출하면서 내부에서 확인하면서 다른 상태로 간다.- 2번 draw()가 될때까지 내부에서 확인한다
- 상태변화 트리거 메서드는 추상체로 응답을 하는 덕분에, 체이닝으로 여러번 호출이 가능하다.
@Test void readyHit() { // given & when : ready -> 2 -> ready -> 10 -> 2장뽑는 순간 Hit상태(or Blackjack)로 최초 Ready객체 탈출된다. final State state = new Ready().draw(Card.of(Suit.SPADES, Denomination.TWO)) .draw(Card.of(Suit.SPADES, Denomination.JACK)); // then assertThat(state).isInstanceOf(Hit.class); }
중요) 최초Ready상태객체 + 트리거 했는데도 아직 Ready상태인 것도 테스트
@Test
void ready() {
// given & when : ready -> 2 -> ready
final State state = new Ready().draw(Card.of(Suit.SPADES, Denomination.TWO));
// then
assertThat(state).isInstanceOf(Ready.class);
}
32) 기존 테스트코드를 전체 리팩토링 ( Ready.start -> new Ready.draw().draw()로)
메서드의 통합테스트
에서 -> 특정 객체 테스트
로 좁혀들어갔으니 -> 개별 상태 객체들도 자기테스트로 테스트를 옮겨가도 된다.
일단
-
테스트 가져가기 (ReadyTest -> HitTest)
-
다시 Ready.start( 재료1, 재료2) ->
new Ready() 재료없는 최초 상태객체로 시작
하도록 리팩토링- 기존 돌아가는 코드의
위에 복붙
후 수정
@Test void hitHit() { // given & when : ready -> draw 2,10 -> hit -> draw 1 -> hit State state = new Ready().draw(Card.of(Suit.SPADES, Denomination.TWO)) .draw(Card.of(Suit.SPADES, Denomination.JACK)) .draw(Card.of(Suit.SPADES, Denomination.ACE)); //State state = Ready.start(Card.of(Suit.SPADES, Denomination.TWO), Card.of(Suit.SPADES, Denomination.JACK)); //state = state.draw(Card.of(Suit.SPADES, Denomination.ACE)); // draw호출이 가능해서 assertThat(state).isInstanceOf(Hit.class); }
- 기존 돌아가는 코드의
33) 개별 상태객체부터 시작하는 Test로 고치기(Ready시작 안해도 됨)
상태값을 만드는 재료만 넣어주면, 그 정보를 가공하여 반영된 객체를 생성
할 수 있다.
중요) 상태값을 가지는 객체라면, 그 -
사실 특정객체Test는 Ready최초시작객체부터 시작할 필요가 없다.
-
**객체 = 최초시작객체가 아니라면, 대부분
재료를 받아 -> 가공 -> 상태값
으로 가지는 로직을 가지므로Hit
객체부터재료를 줘서 만들어서 테스트
하면된다. ** -
hit 만드는 테스트부터 만들자.
-
사용성을 고려하여 생성자 추가
는 해도 괜찮다. -> new단일객체()도 귀찮은데 2개이상 생성하면서 List.of()로 묶어줘야한다? -> 클라이언트 배려한 가변인자 파라미터 빨간줄 생성
참고) 객체 생성시, 재료를 넘겨줄 때,
List.of()로 묶어서
전달한다? -> 인자는 콤마로 + 파라미터는 가변으로 수정 by Ctrl+F6(change signature)
리팩1) 외부에서 인자전달시 -
인자에 new 생성자(
List.of(객체1, 객체2)
) 형태라면-
new 생성자( ` 객체1, 객체2
) **로 **
List.of()의 묶어주는 과정을 제외하여 빨간줄 생성`한 뒤, 생성자를 추가해주자. -
카드 2개로 인식하지만, 우아한 파라미터 개선의
change signature ctrl+F6
으로가변인자 파라미터
로 바꿔주자 -
부생성자일 것이니 위치를 위로 옮겨가서 this()를 활용한다.
-
배열 -> List로 바꿔줄려고
배열.stream
을 썼으나…
-
참고) 가변인자는 밖에서 없애줬떤 List.of를 그대로 쓰면 된다. stream대신 -> 내부 List.of()로 처리
public Cards(final Card... cards) {
this(List.of(cards));
}
@Test
void hit() {
final State state = new Hit(new Cards(Card.of(Suit.SPADES, Denomination.TWO),
Card.of(Suit.SPADES, Denomination.JACK)));
assertThat(state).isInstanceOf(Hit.class);
}
34) 카드 2장으로 new Hit() 시작하여 hit -> hit 테스트
- 2장으로 Hit를 만들고
- 체이닝으로 트리거메서드를 호출해했지만
- 가독성이 엉망이다.
- 체이닝으로 트리거메서드를 호출해했지만
긴 파라미터부분을 -> 위에 변수로 추출
리팩3) 가독성을 위한 -
선택된 부분이
윗줄에 변수로 추출
되어야한다.- 파라미터 추출은 메서드의 파라미터로 이동된다. 조심
@Test void hitHit() { // 2,10 으로 hit -> draw 1 -> hit final Cards cards = new Cards(Card.of(Suit.SPADES, Denomination.TWO), Card.of(Suit.SPADES, Denomination.JACK)); State state = new Hit(cards); //when state = state.draw(Card.of(Suit.SPADES, Denomination.ACE)); //then assertThat(state).isInstanceOf(Hit.class); }
상수기반 단일객체
는 Fixtures
클래스에 모아두고 static import해서 단일객체
가 객체지만, 상수처럼 갖다
쓰자.
리팩4 & 중요) 많이 쓰이는 Test속 일급재료인
Test에서 자주쓰는 단일객체
(일급의 재료)를 Fixures
클래스에서 상수로 선언하여 모아두자.
-> new 단일()
or 단일.of()
/ 단일.from()
등 인자에서 호출되는 단일객체 생성자(정펙매) 호출부분을 ctrl+alt+C
로 객체를 상수추출
한 뒤, F6 -> public(psf)
으로 Fixtures 클래스로 옮겨주자.
중요) - cf)
psf
= public static final -> 다른데서 갖다쓰는 상수 -> Test Fixture아니면 쓸일 없을 듯 -> 상수라도 각 도메인내부에서 쓰기! - cf)
pr sf
= pr ivate static final -> 일반 클래스내 상수
-
Fixtures
클래스를 만든다.- breadcrums(c+a+;)를 이용해서 같은 테스트내에서 편하게 만들 수 있다.
-
상수가 아니라
new 단일()
or단일.of()
/단일.from()
등 인자에서 호출되는 단일객체생성자를 추출해와야한다.- 오른쪽에 복제가 아닌 1개 창으로 띄우기
ctrl + \
(복제) -> 왼쪽으로 넘어오기(tabmover의c+a+s+[
) -> 왼쪽 창끄거나 다른창으로넘어가기
- 오른쪽에 복제가 아닌 1개 창으로 띄우기
-
직접 Fixutre에서 상수로 선언하지말고
상수로 추출 -> F6으로 상수옮겨가기
를 해보자.- ctrl+alt+ C의 상수추출
-
상수 부분을 클릭
후F6
으로 이동- 실수로 Card 등의 Class부분을 잡고 F6을 하면, class이동이 되어버리니 조심
-
갖다 쓸 상수(public sf)로 가는 것이기 때문에
Public
도 지정해준다.- 안해주면 default생성자가 되어버린다.
- Fixtures클래스로 public으로 옮겨갔으며 + 기존 있던 Test에서는
Fixtures.상수
로 사용되어진다.
Add on-demand static import
를 통해 편하게 현재클래스내 상수처럼 쓸 수 있게 한다.
참고) 테스트용 갖다쓰는 객체상수 클래스 Fixtures는 -
상수전용 클래스 Fixture를 static import로 생략하고
상수(SPADE_TWO)
만 사용할 수도 있다. -
상수객체가 정상 단일객체럼 행동하는지
해당 객체 테스트
에서객체상수
isSamaAs생성한 객체
테스트를 해보자-
기존 테스트
@Test void of() { final Card card = Card.of(Suit.HEARTS, Denomination.ACE); assertThat(card).isSameAs(Card.of(Suit.HEARTS, Denomination.ACE)); }
-
사용되는 단일객체를 -> 상수객체로 뽑아서 옮겨놓고 상수로 가져오자.
-
-
나머지 사용되는 단일객체들도 다 상수화 -> Fixutre에 public 상수로 옮기기 해주자
public class Fixtures { public static final Card SPADE_TWO = Card.of(Suit.SPADES, Denomination.TWO); public static final Card HEART_ACE = Card.of(Suit.HEARTS, Denomination.ACE); public static final Card SPADE_JACK = Card.of(Suit.SPADES, Denomination.JACK); //... }
ctrl+H
로 일괄변경해주기. -> 의심되는 객체는 ctrl+shit+F
로 Fixtures에 만들어놓은게 없나
살펴보기
참고) 객체 -> 상수 만들다가 기존 Fixtures에 뽑아 놓은 객체상수 발견시 -> -
상수 만들었다, 이미 만들었떤 것이 나온다? -> 찾아바꾸기로 한번에 다 바꿔주자.
-
Fixture 목록에 없는 것들이 나타나면, 또 뽑아서 바꿔준다.
-
이미 만들어놓은 것 같은데 확인하고 싶다면, ->
블럭
+ctrl+shift+F
에서 Fixtures에 걸리는게 있나 훔쳐보기-
있다면 windows에서 클릭해서 해당 Fixture상수 복사해와서 -> ctrl+H에 복붙여넣고 찾아바꾸기
-