강의) 네오 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의 카테고리인 상태객체로 바꿔보기
27) Ready도 스태틱 로직메서드를 위한 class가 아니라 State로 만들어보자
상카(추상체) 구현을 시작으로 카테고라이징 된다.
-
현 class상태에서
State를 구현하여 카테고라이징

- impl해야하는 메소드를 가장 위로 올려, State임을 나타내주자
- impl해야하는 메소드를 가장 위로 올려, State임을 나타내주자
[잘못설계] Ready 상태는 start(card1, card2)를 활용하여 Hit or Black상태임을 상태값으로 가지고 있다?
- 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) 인페를 상카로 뽑아 카테고라이징 했다면, 구현체의 단독 상태, 메서드 개발은 자유롭다 -> 규칙에 맞는 메서드만 오버라이딩해서 개별구현하면 된다.
중요) 시작 State도 마찬가지로 다른 구현체(상태)로 넘어갈지 판단에 필요한 정보 cards를 상태값으로 가진다 -> 재료로 받진 않지만, 추가정보를 받아서 채우면서 판단 -> 상태값인 cards의 갯수에 따라 현재상태인지 -> 다른 상태로 넘어가야할지 판단할 수 있다.
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; } -
업데이트된
상태값으로 -> 일급이니 메세지를 보내서 다음 객체를 판단한다
30) Ready가 가능한 상태를 생각해본다 -> 1장씩 받으니 Ready 그대로 or 2장 다받으면 Hit or Blackjack -> 업데이트된 상태값으로 isReady()로 물어봐야한다.



-
아직 카드갯수가 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()로 묶어줘야한다? -> 클라이언트 배려한 가변인자 파라미터 빨간줄 생성
리팩1) 외부에서 인자전달시 List.of()로 묶어서 전달한다? -> 인자는 콤마로 + 파라미터는 가변으로 수정 by Ctrl+F6(change signature)

-
인자에 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); }
리팩4 & 중요) 많이 쓰이는 Test속 일급재료인 상수기반 단일객체는 Fixtures클래스에 모아두고 static import해서 단일객체가 객체지만, 상수처럼 갖다쓰자.
중요) 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.상수로 사용되어진다.
참고) 테스트용 갖다쓰는 객체상수 클래스 Fixtures는 Add on-demand static import를 통해 편하게 현재클래스내 상수처럼 쓸 수 있게 한다.
-
상수전용 클래스 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); //... }
참고) 객체 -> 상수 만들다가 기존 Fixtures에 뽑아 놓은 객체상수 발견시 -> ctrl+H로 일괄변경해주기. -> 의심되는 객체는 ctrl+shit+F로 Fixtures에 만들어놓은게 없나 살펴보기
-
상수 만들었다, 이미 만들었떤 것이 나온다? -> 찾아바꾸기로 한번에 다 바꿔주자.




-
Fixture 목록에 없는 것들이 나타나면, 또 뽑아서 바꿔준다.

-
이미 만들어놓은 것 같은데 확인하고 싶다면, ->
블럭+ctrl+shift+F에서 Fixtures에 걸리는게 있나 훔쳐보기
-
있다면 windows에서 클릭해서 해당 Fixture상수 복사해와서 -> ctrl+H에 복붙여넣고 찾아바꾸기

-