TDD 총정리3-블랙잭 구현을 통한 상태패턴과 랜덤로직 전략패턴
TDD학습 내용 압축 정리
📜 제목으로 보기
- 랜덤을 셔플로 구현하여 데이터를 제공해주는 Deck에서, 랜덤부분을 전략패턴으로 만들고, 외부에서 [직접 반환되는 객체를 만들어놓고 -> 최종 객체만 람다식에 넣어] 제공함.
블랙잭 상태 패턴 구현
-
컴퓨터내부에서 로직의 시작인 card 2개가 주어진다면, 정제된input이라 생각하고 카드부터 만든다.
- 제한된 종류의 값 = 상수묶음은 enum으로 만들어서 카드를 만든다.
- 테스트메서드명은 인자 -> 1case 가 결정한다.
-
4 x 13 종류의 객체는 캐싱이 가능하다.
- 캐싱을 적용하려면 일단 생성자 인자 그대로
정팩메
로 만들고, 기본생성자는 private해준다
- 캐싱을 적용하려면 일단 생성자 인자 그대로
-
원래는
기본생성자의 1개 인자
에 대해 hashMap<생성자인자, 객체>
형으로 static CACHE map을 만들어야하지만,파라미터가 2개일경우, 1개로 합친 Key
를 만들어야한다.- 2개이상의 정보를 묶은 class를 만들어도 되지만, string을 이용해서 합쳐 1개의 key로 만들어
<String, 객체>
형을 많이 사용한다. - 캐쉬의 기본공식은 꺼내서 없으면 put해주고, return get을 반환이다.
- 2개이상의 정보를 묶은 class를 만들어도 되지만, string을 이용해서 합쳐 1개의 key로 만들어
-
테스트메서드명도 함수를 from or of의 정펙메로 바꿔주고, isSameAs로 테스트한다.
-
java의 기능으로서 없으면 key에 대한 value를 생성해주고 있으면 반환하는
computeIfAbsent
를 사용해서 리팩토링한다.- 또한 캐싱될 객체의 수를 알고 있으면 미리 넣어줘도 된다.
-
CACHE대상객체는, 거의 값객체기 때문에, 값으로 비교하기 위해 eq/hC오버라이딩도 해준다.
-
이제
가진 2장의 카드
로정해진 종류의 행위마다 변화하는 상태 판단
하면서,Game
을 풀어 가야한다.- 카드2장 -> if 상태판단 ->
상태에 따른 행위 -> 다른 상태
if 상태에 따른 행위의 결과가 다른 상태가 나온다면, 상태패턴
을 고려한다- 2장의 받은 상태에서
- if 21 -> 블랙잭 상태 -> stop
- if 21미만 -> hit상태 -> stay or hit지속
-
가장 만만하게 응답될 상태의 input
부터 case를 만들고 -> 테스트메서드명으로 시작하면 된다.hit가 output상태로 나오는 input을 넣어주자.
- 카드2장 -> if 상태판단 ->
-
Service가 아닌 만들어낸 input부터 시작하는 Game클래스를 만들고, 서비스메서드처럼 static으로 start()를 만들고 재료를 넣어주자.
- input으로
hit대상이되는 카드2장을 인자
를 넣어주고,응닶값이 hit상태
이어야한다.
- input으로
-
다음은 Game.start()가 blackjack상태를 응답하도록 작성해보자.
-
테스트에 인자를 blackjack으로 넣어주고
-
blackjack시 ACE가 11로 사용될줄알아야한다. enum에 매핑된 값이 필요함을 인지한다.
-
enum에 값이 매핑되는 순간부터 바로 getter를 작성
해주면 된다.-
ACE는 1or11을 가질 수 있으나
규칙상 상한이 21점으로 제한되어있다면, 11을default시 2장을 아예 가질 수 없기 때문에, default를 작은 수로 주고 보정
한다
-
-
card입장에서는 enum getter -> enum point getter 2번을 거쳐야하므로, getter를 래핑해서 만들어준다.
-
-
2장의 카드 중에,
ACE가 포함되어 있을 경우
&&[11로 쓸 때, 합 21] -> [1로 쓰면 합 11]
일 경우만, blackjack이 되도록 처리해줘야한다.-
2개의 카드 중에 ace가 있는지 확인한다.
-
일급컬렉션이 아닌 상태에서
동일형 객체 일괄처리
는 List.of()나 Stream.of()를 사용해서 묶어서 처리한다. -
있는지 없는지 판단
은filter + count
가 아니라anyMatch( -> 개별요소판단 )
로 판단한다.
-
-
ace를 가졌다면 && 2장의 합이 11이하인지 확인한다
- 11이하이면, ace는 10으로 쓰일 수 있다.
- 11이면, ace를 가진 경우 blackjack이다.
-
즉, ace를 작은값로 유지하고, 기준값을 큰값일때로 잡아서 처리하면 된다.
- ace(1) + 10 -> 11이면 21로 블랙잭
- ace(1) + x -> 11미만이면, ace(11) + x
- ace(1) + 11 -> 12이상이면, 교환불가
- ace가 2장있다면? 변환될 수 있는 ace는 한장이다. 2장 변환시 이미 22가 되어버림
-
-
테스트를 통과했다면, 리팩토링한다.
-
일급컬렉션이 아닌
같은형 객체 일괄처리
를 위해 List.of()로 묶었지만,같은형끼리의 단순집계
또한 List.of()로 묶어서 누적연산으로 처리가능하다.- 같은 context에서
일괄처리
와같은형 끼리의 연산
이 뭉쳐있다면, List.of()나 Stream.of()로 묶어주는 것은 공통코드가 된다.파라미터 추출로 각각을 빼낸뒤, 1개의 변수로 대체
할 수 있다.
- 같은 context에서
-
같은형끼리의
단순연산
or일괄처리
로 인해2개이상의 같은형을 List.of
로 묶었다면,List.of()로 묶는 곳에서 일급컬렉션
을 고려한다.-
Stream.of()는 List.of()로 바꿔서 일급컬렉션의 생성자 인자로 올리자.
-
이후
인자없는 생성장에서 빈컬렉션으로 초기화
하여, add가능한 일급컬렉션으로 수정하자. -
인자없는 생성자는
부생성자
이므로빈컬렉션 초기화를 this()
에 넣어서 초기화해주자- add시 새객체 반환시, 얕은복사 안해줘도 add전 객체는 버려져서 오염될 일이 없을 것 같아서 빼줬다.
-
-
-
책임위임을 위임하려면, 일단
현재context상의 메서드의 파라미터
에 걸려있어야 하며,static메서드라면, static키워드를 삭제하고 위임
해야한다.- getter를 쓰더라도 파라미터로 들어가서 완성해놓자.
- static메서드라면,
static키워드를 지운 뒤 f6으로 위임
해야한다 - getter를 썼다면, 위임된 객체에서는 내수용getter가 사용되고 있으니 지우고, 필드로 바꾸자.
-
또다른 리팩토링으로서
조건식에 하나의 도메인에 대한 메서드호출이 나열되어있다면, 메서드 추출시 파라미터에 1개만 도메인만 걸리며, 이 또한 묶어서 책임을 위임
할 수 있다.-
역시 static안이라면, static을 지우고 위임한다.
-
-
상태패턴 적용을 위해 state라는 추상체 인터페이스를 만들고, 응답형을 Object가 아닌 추상체로 주면 ->
알아서 응답되는 객체들을 구현
하라고 intellij가 알려준다. -
테스트코드의 응답형에 따른 변수형도 변경해준다.
-
추상체를 만드는 순간부터
패키지를 분리하여, 패키지폴더 대상 다이어그램으로 의존성을 확인
하자.- 이후,
구상체만의 메서드 개발후 올리기전 다이어그램 + CompareFile하자
- 이후,
-
상태패턴을 도입했으면,
응답값으로 나온 현재 상태
를 바탕으로추상체에 있는 [다음상태로 갈 수 있는 인자]를 받아 [다음상태로가는 메서드]
를 정의해줘야한다.에러호출로 종료되는 제일 쉬운 현재상태(blackjack)부터 [다음 상태로 넘어가는 메서드]를 만들자
- 테스트메서드에 작성한
응답된 상태
- Hit
- Blackjack -> draw즉시
에러 호출로 종료
라서 더 쉽다.
- 테스트메서드에 작성한
-
이 때,
특정 쉬운 구현체부터 메서드 작성
이라면,공통메서드일지 모르니, 오퍼레이터로 만들지말고 다운캐스팅해서, 구현체만의 메서드
로 만든 뒤,공통이면, @Override
해서 올린다.- 응답값을 받을 때 (특정구현체) 다운캐스팅을 하고, 메서드를 작성한다
-
특정구현체의 메서드이며,
thr 던질 메서드
라도미래에 공통으로 사용될 예비오퍼레이터라면, 응답형을 지정
한뒤 던진다.- 게임종료는 thr로 한다.
-
다운캐스팅된 구상체만의 메서드 개발이 완료
되면,다이어그램을 확인해서, 올려도 되는지 판단
한다.-
다이어그램 + CompareFile을 펼쳐 모든 구상체가 호출해도 되는 메서드인지 확인한다.
- 다이어그램 단축키 : ctrl + alt + shift + U
- compareFile : 구상체들만 선택후 ctrl + D
- 올릴 거면, 다운캐스팅했떤 로직을 삭제하고, 다른 구상체들도 구현한다.
-
다이어그램 + CompareFile을 펼쳐 모든 구상체가 호출해도 되는 메서드인지 확인한다.
-
Override + pull members up으로 오퍼레이터로 올린 뒤,
테스트상 다운캐스팅한 로직은 삭제
해준다 -
blackjack -> 게임종료(thr)는 끝났다. 이제
hit상태에서 -> draw or stay
를 할 수 있다.- 일단 draw부터 만든다.
-
hit상태에서는
- 합 20이하라면, hit상태
- 합21이라면, blackjack상태
- 합 21초과라면, bust상태이다
테스트에서는 case마다 인자로 넣어줘서 차근차근 개발하며, 제일 쉬운 것(hit)
부터 응답하게 만들어나가면 된다.
-
hit응답상태에서
합 20이하가 되는 인자
를 넣어줘다시 히트상태를 응답
하도록 case를 만든다.- 첫 case만 만들 땐, 빠르게 return이라 연산식이 없다. 하지만,,,
-
아직까지는 hit만 빠르게 반환하도록 만든다.
-
이제 hit에서 2번째 쉬운 상태응답case인, hit -> bust로 간다.
- 테스트를 작성하고
-
Bust 클래스를 만들고, State구현한 뒤,
hit의 draw()의 내부 로직을 작성해야하는데..
-
Hit의 트리거메서드인 draw의 2번째 case Bust를 판단하려면,
기존 카드정보를 모두 상태값으로 쥐고 있고 vs 메서드 인자로 넘어온 정보를 통해 상태업데이트 후 새객체반환
해야한다.
-
최초의 상태객체를 만들어내는
Game.start()
에서현재정보를 생성자의 인자로 넘겨줘 상태값으로 가지고 있어야
한다.-
trigger + 정보를 판단하려면,
상태객체는 생성시부터 이미 정보를 상태값으로 가지고 있도록
해야한다. -
최초로 상태객체가 만들어지는 곳에서
현재 상태를 결정하는 정보를 생성자 주입해서 물고 있자.
-
기존에 생성자 없이 사용하던 객체에,
상태값이 추가되어 생성자가 추가될 경우
,부생성자로 빈값할당
으로 초기화해주는기본생성자
를 추가해서기존코드가 망가지게 않게 한다
-
또한,
trigger메서드내에서는 업데이트된 상태값으로 새 상태객체를 생성해서 반환
해줘야한다.- 상태값도 포장된 일급컬렉션이라, cards + card를 처리해야한다.
-
-
같은형의 일급vs단일라면 메세지를 보내서 처리해야한다.
주로 add가 쓰일 것이다.
- add를 만든다면, list + add로 인해 상태변화된 불변일급컬렉션을 반환해줘야한다.
- 이 때,
기존 상태값(컬렉션 필드)를 얕은복사
후 add해야한다. -
컬렉션 파라미터의 사전검증으로서 일급컬렉션에서 add하여
새로운 컬렉션 상태
를 만들어,새로운 일급컬렉션 객체
를 불변하게 반환할 때는,기존상태값을 얕은복사해서 연관성을 떼어내야한다
-
상태필드가
객체이상, 일급컬렉션인 경우
,현재 구체적인 값의 상태를 물어볼땐 getter대신 메세지
를 보낸다-
상태필드가
객체이상, 일급컬렉션인 경우
,현재 구체적인 값의 상태를 물어볼땐 getter대신 메세지
를 보내서값으로만 응답
받는다.- 이미 가진 카드들의 합을
묶어서 연산
하도록 프로그래밍 되어있으니,갯수가 늘어나도 합 연산은 그대로 유지되고, 그 메서드를 내수용으로 사용
하면 된다.
- 이미 가진 카드들의 합을
-
상태값이 객체이상이라면, 메서드를 보내서 현재상태를 물어본다
-
상태필드가
-
현재 진척도
- hit or blackjack
- blackjack -> 뽑을 시 예외발생해서 종료
- hit
- hit
- bust -> 뽑을 시 예외발생해서 종료 « 먼저 처리해주자
- blackjack?
- hit or blackjack
-
종료상태
객체는trigger메서드 호출시 예외발생으로 종료
까지 마무리해줘야한다.- Blackjack상태객체, Bust상태객체 (앞으로 Stay도 종료상태일 것임)
-
참조변수 재할당시 람다캡처리 문제가 발생한다. 체이닝가능한 메서드는 체이닝해주자.
-
setNext, add와 같이
같은형 객체
를 반환하는 메서드들뿐만 아니라같은카테고리인 추상체
를 반환하는 메서드들도체이닝 메서드
이다.- 객체반환메서드는 체이닝을 생각하자.
- 이 때, static메서드로서 게임출발을 담당했던 메서드도
State
를 반환하는 메서드이므로체이닝 가능
하다
-
2장인데, ace를 안 가진상태에서도 블랙잭이 될 수 있다. hit -> blackjack을 개발해보자.
- hit or blackjack
- blackjack -> 뽑을 시 예외발생해서 종료
- hit
- hit
- bust -> 뽑을 시 예외발생해서 종료
- blackjack -> ace를 가졋다면 합 11 ,
ace가 없다면 합21
시 만족
- hit or blackjack
-
핵심로직을 시작하는
카드2장input -> 상태객체 output
하던 스태틱 클래스도Ready 상태
의 상태객체다.-
Game -> Ready 수정
-
테스트의 Ready.start() -> 찾아바꾸기로 new Ready().start()로 수정하자.
-
-
State인터페이스 구현 후 start보다
구현메서드draw()를 가장 위로
- 상태를 구현한 구현객체라는 의도를 명확히 하기 위해서는
구상메서드를 가장 위로
올린다.
-
상태객체 패키지로 이동시킨다.
- 상태를 구현한 구현객체라는 의도를 명확히 하기 위해서는
-
-
서비스 메서드 == 스태틱 메서드 like 유틸메서드
에서 ->객체
가 되었다면-
상태값으로 카드 2장을 받는다.
-
상태값을 만드는 input들은 다 생성자로 들어와야한다.
- 메서드로 상태값 정보가 들어온다면, setter다. 지양해야한다.
-
서비스의
유틸메서드 input
->생성자로 주입
된다 -
서비스의
유틸메서드내 로직을 거친 뒤 1개의 output응답값(상태객체)
->필드
로 가진다.
-
-
start의 카드2장의 input은
생성자로 주입
되고, 1개의 output인 상태객체는 필드가 되어상태객체를 초기화
해서 최초로 가지는상태 포장객체
가 Ready가 된다.- 즉, 서비스메서드는 나중에
첫 상태객체를 초기화해서 사용하는 포장객체
가 되는 것이다.
- 즉, 서비스메서드는 나중에
-
이렇게
Cards
가 아닌다른 상태객체
를 가지게 되면,상태객체가 아니라 상태객체를 사용하는 객체
가 된다.- 하지만,
Ready
라는 상태객체도상태를 사용할 객체가 State state의 초기값으로 가져야할
객체로서 엄연히 존재해야하며다른 상태객체들처럼 draw()를 구현
하여다른 상태로 넘어가야 한다
- 하지만,
-
정리하면
-
Ready도 다른 상태객체들과
동일 상태값 필드
(Cards)를 가지며생성자로는 아무것도 주입 안된 빈카드로 초기화
하는기본 생성자
로 만들어져야한다. -
Ready도 .start()로 다른 객체로 넘어가는 것이 아닌 다른 상태객체들의 메서드처럼
Trigger메서드(draw())
로부터 setter정보를 받아상태값업데이트 이후 상태값을 가지고 판단하여 다른 상태로 넘어가
도록 수정한다.- Ready는 카드를 2장을 받는다. -> 다른상태객체처럼 draw로 1장씩 받도록 정의하고
외부에서 2번을 호출
하던지다른 파라미터로서 받도록 오버로딩메서드
로 정의해준다.
- Ready는 카드를 2장을 받는다. -> 다른상태객체처럼 draw로 1장씩 받도록 정의하고
-
-
빈생성자 -> 빈 상태값으로 생서될 수 있도록 생성자를 수정하고, 다음 상태객체로 넘어가는 로직을 draw로 옮긴다.
-
문제는
Ready는 1장이 아니라 2장을 다 받은 상태에서 판단이 이루어져야하는데, 1장만 받고 hit로 바로 가고 있다.
-
Ready가 2장을 받은 상탠지 확인하는 것은
Ready의 상태값인 cards
로 판단해야한다.- 객체이상이므로 메세지를 던져 물어본다. 너 2장 받았냐고, 아직 2장미만의 Ready상태냐고
-
1장 받고 상태값이 바뀌면, 새객체를 반환해야줘야하므로 다시 new Ready(바뀐cards)로 응답한다.
- cards로도 만들어져야하므로 생성자가 추가되고, 기존 생성자는 부생성자가 된다.
-
밑에 로직도 업데이트된 상태값으로 생성하도록 수정한다.
-
test에선 다 start(카드, 2장)으로 작성된 상태다.
코드 수정은 기존코드start를 복사해서 수정draw
하고 나서 지운다.
-
서비스 or 핵심로직 통합테스트가 Ready라는 1개 상태객체 테스트로 바꼈다.
-
개별 상태객체에 해당하는 코드들은 각자의 테스트로
나누어서 옮긴다. -
비록 Ready에서 시작하지만 일단 옮겨간다.
-
-
Ready부터 시작하는 테스트를, 해당객체부터 시작하도록 수정한다.
- Ready는 인자없는 생성자에서 draw()2번했지만
특정상태 객체는 cards를 상태값으로 주고 시작
하도록 변경하면 된다.
-
파라미터 변경한 생성자 추가해주기 ->
파라미터 추가는 해당 파라미터로 생성자 추가한 뒤 오버로딩으로 처리
한다고 했다.-
client가 편해야한다.
-
Cards를 만드려면 List.of() 로 묵어줘야하니, 내부에서 묶어주고 가변인자로 주도록 변경해보자.
-
기존
-
인자입력시 List.of() -> 가변배열
로 입력하도록 변경한다.
-
-
-
상태변화가 없는 캐싱 객체
는 싱글톤이라,testutil패키지 > Fixtures클래스
안에상수객체
로 만들어 써도 된다.-
test루트에서
testutil
패키지를 만들고 내부Fixtures
를 만들자
-
-
Fixtures에 쓸려면
어디서든 불러도 같은 객체가 생성되도록 캐싱==싱글톤이 보장
되어야한다.- isSameAs로 확인해야한다.
- 만약, 캐싱객체가 아니더라도,
eq/hC를 재정의하여 값이 같으면 같은 객체가 되는 값객체
가 되어야한다. 이 땐, isEqualTo로 정의한다.
-
**싱글톤 캐싱객체 or VO라서,
생성해서 쓰면 무조건 같은 객체
가 보장되었다면,테스트에서 쓰인 객체들의 상수추출 -> Fixture로 옮겨서 재활용
할 수 있다. **- 상수가 되면 편하게 생성안하고 편하게 쓰면 된다.
한글로 Fixture를 생성해서 쓰자
- 상수 추출후, Fixture로 옮기기전에 미리 다 상수로 바꿔놓으면 더 쉽다(
ctrl+H
)
-
이제
빈 생성자로 빈 상태값으로 시작하는 Ready의 [시작상태객체]
가 완성되었다. -
시작객체
가 있다면,같은 패키지로 몰아서 시작객체public을 제외한 나머지들은 default가시성
을 가지도록 해서, 외부에서 생성안되도록 한다면,사전검증을 안해도 된다.
-
만약, 생성자가 기본생성자라서 정의를 안해줬다면,
재정의 후 가시성변경
해줘야한다.- 대표적인 예가
유틸클래스(상태값없이 input->output static메서드만 가지는)는 생성자를 private가시성으로 변경
해주는 것이다. - 생성자가 없는 클래스 ->
public으로 열여있으며, 재료를 바탕으로 상태값을 만들지 않는 클래스
- 대표적인 예가
- client가 잘못입력할 수도 있기 때문에
중간에 생성되는 상태객체들
은외부에서 생성못하도록 가시성으로 막아주기
-
만약, 생성자가 기본생성자라서 정의를 안해줬다면,
-
상태객체는
trigger메서드(draw)
외에중도 stop
메서드와stop상태
를 가질 수 있다.-
ready + trigger
- blackjack
- hit + trigger
- bust
-
blackjack: 블랙잭은 2장일때만 가능이다. - hit
- hit + stop : 중도stop 메서드
stay
: 중도 stop상태객체 반환
-
stop메서드와 stop상태객체 2개를 다 만들어준다.
-
-
hit상태에서 stay를 호출하도록 테스트를 짠다.
-
메서드가 반환해야할 상태객체부터 만들고, State 추상체상태의 hit에서 stay()를 만들면, 오퍼레이터로 등록된다.
-
각 구현체는 외부에서는 추상체변수에서 다형성으로 사용되니,
특정 구상체(hit)에서 빨간줄 메서드로 만들면, 추상체인 인터페이스의 오퍼레이터
로 올라간다.-
일단 인터페이스에 생성하고 -> 해당 구현체만 일단 구현해보자.
-
-
특정 구상체에만 사용되는 메서드
가 등장했다.-
상속관계면, 조합관계로 바꾼다.
-
이미 인터페이스를 구현하는 조합관계의 구상체였다면
- 일단 인터페이스에 올리고, 특정구상체만 자기메서드를 로직을 구현한다.
- 마저 테스트를 진행해야하니
나머지 구상체들은 로직 없이 구현만 해준다
- 이후
해당메서드 테스트가 끝
나면,특정구상체 외 사용하지 않는 구상체들에게 내리기 전
에중간에서 막아주는 추상클래스로 만들어지는 중간추상층
을 만들어준다.
-
일단 모든 구상체들도 구현은 하고 특정 메서드의 테스트를 진행한다.
-
-
사용하지 않는 메서드를
억지구현한 코드가 중복
이라서해당하지 않는 구상체들만 묶은 중간추상층(추상클래스)
를 도입하고, 거기서 구현해준다.-
중간추상층은 구상체로 내려가는 메서드를, 중간에 구현하여 중복되는 코드를 가운데서 막아준다.
- 이 때, 중간추상층은 카테고리로서,
Ready는 같이 묶기엔 의미가 다르다
- 이 때, 중간추상층은 카테고리로서,
-
일단 인터페이스를 구현하는
중간추상층
추상클래스를형용사 상태
네이밍해서 만들고- stay할 수 없는 것들은
이미 끝난 Finished
라고 하고 - Ready는 논외로 성격이 달라, 놓아두고
- Hit도 구현하므로 놓아둔다
- stay할 수 없는 것들은
- 해당하는 구상체들은 추상클래스를 상속한다
- 사용하지 않는 메서드는
thr IllegalState
로 처리한다.
-
-
State를 구현한 추상클래스Finished를 만들고
- abstract를 달고, 2개의 오퍼레이터 모두 구현해주되
- 먹어줄 것(stay -> 사용안됨)만 남기고, 개별구현할 것들은
@Overide 구현 메서드들을 자식들에게 다 내려보낸다
-
하위 구상체들은 인터페이스 impl이 아닌 중간추상층인
Finished
를 extends한다
-
추상클래스 도입시
중간에 먹어줘서 공짜로 물려주는 것들은 final을 달아서 자시이 수정 못하게 막아야한다
-
구상체가 도입되고, 추상체도 추가되었으니 다이어그램을 본다.
-
추상클래스 도입후
눈꽃모양의 final
을 확인하자.
-
-
모든 구상체에 대해
stay()
메서드가 추가되었으니 메서드 테스트를 추가한다.- blackjack, bust, stay -> finished -> stay호출시 예외
- ready -> stay호출 시 예외
- Stay 도메인객체가 추가되었으니, 생성 및 메서드 테스트를 해야한다.
-
상태패턴의 상태객체반환 with trigger되는
결국엔 다른 객체의 상태값으로 포장
될객체
들은view에 넘기기 위해 getter를 무조건 가진다
-
trigger 등 set계열 메서드들 개발이 완료되면 getter도 개발
해야한다. -
원래
추상클래스의 중복필드처리
는부모필드는 생성자 정의없이 -> setter만든 뒤, setter주입
되어서자식이 super를 못쓰게 해야하는데..
-
-
복잡한 구조에서
getter
개발하기- 현재는 복잡한 구조로 되어있기 때문에,
메서드 개발을 1개의 구현체에서부터 올리기
를 하진 못한다.최상위 인터페이스State에서 정의후 하나씩 내려오기
해야한다.
-
최상위 인터페이스에 getter를 정의하되
값의 반환
이 아닐 때는.get필드()
대신.객체()
형식으로 정의해본다.- 인터페이스에서는 접근제한자가 public이 default이므로 두고 반환형을 상태값 Cards로 주면서, 메서드명도 cards()의 필드명으로 만들자
-
개별 구상체(Ready, Hit)는 바로 구현하되,
중간추상층 Finished
에서는 해당오퍼레이터가 중복인지 확인한다.-
getter오퍼레이터의 구현은
getter구현 전에 필드값이 존재
해야하는데-
중간추상층의 부모가 없다면, 바로
자신의 필드를 반환
해주면 된다. -
Ready / Hit 구상체
-
-
**
중간추상층에서 먹어준다면, [좋은 부모로서 생성자없이 setter로 -> private필드올릴 준비]를 해야한다
**
-
-
3개 구상체에 대해
공통getter
라고 판단이 들었으면,final 템플릿
메서드로서중간에서 먹어준다
-
추상클래스는 무조건 public final or protected abstract다
-
-
문제는,
getter로 반환될 필드를 부모인 중간추상층에 올리는 순간
, 부모의 생성자가 정의되어자식이 super를 쓰는 불상사
가 생길 수 있다. -
부모는 해당필드에 대해
생성자가 아닌 public final setter
로 객체를 초기화시키며-
해당 부모의
구상체 자식들은 안보이지만 setter를 이용해서 해당 필드를 채워야한다
-
맞는지 모르겠지만, setter로 인해 상태값 변화했으면, 변화된 객체를 반환해줘서
체이닝
가능하게 한다-
예시
-
-
-
기존에 cards를 생성자 주입했던 구상체들은 setter로 변경해야한다.?!
-
- 현재는 복잡한 구조로 되어있기 때문에,
6. **부모가 `getter를 먹으려면 + 공통의 필드 cards`까지 먹어야했다.**
![image-20220806141621141](https://raw.githubusercontent.com/is3js/screenshots/main/image-20220806141621141.png)
7. **자식들은 getter구현 안한 상태 유지 + `공통필드 + 그 생성자까지 제거`해줘야한다.**
![dbbd4ebb-90b5-4ed0-a344-3de8cfc09e02](https://raw.githubusercontent.com/is3js/screenshots/main/dbbd4ebb-90b5-4ed0-a344-3de8cfc09e02.gif)
![image-20220806141930165](https://raw.githubusercontent.com/is3js/screenshots/main/image-20220806141930165.png)
8. **자식들이 그동안 테스트에서 `new Blackjack( cards )`를 사용한 부분은**
1. **부모에 위치하는 `부모 속 공통필드에 템플릿setter로 공급`하고 -> `템플릿getter`를 사용해서 해당 필드를 조회할 수 있다.**
![b01ac58e-55d8-4730-aa34-d4007ea55429](https://raw.githubusercontent.com/is3js/screenshots/main/b01ac58e-55d8-4730-aa34-d4007ea55429.gif)
2. **생성자주입으로 사용하던 객체를 -> [부모공통필드로 옮기는 바람에 setter로]사용해야된다면, 코드변화가 많지는 않다**
1. new 객체( 주입 ) -> new 객체**( ). setter**( 주입 )
![image-20220806142627252](https://raw.githubusercontent.com/is3js/screenshots/main/image-20220806142627252.png)
![image-20220806142650775](https://raw.githubusercontent.com/is3js/screenshots/main/image-20220806142650775.png)
9. **getter구현이 완료되었으면 getter에 대해서 생각해보기**
- **만약 view로 반환하는 객체가 `불변객체(불변일급 -> 상태변화시 새객체 반환)`라면, `DTO가 필요없고, public getter가 가능`하다.**
- 객체의 **상태를 변화시키는 set계열을 호출해봤자 `내부에서는 새객체를 반환`되는 메서드만 제공**된다.
![image-20220806145015195](https://raw.githubusercontent.com/is3js/screenshots/main/image-20220806145015195.png)
- **객체의 상태를 꺼내보는 get계열을 호출해봤자 **
- **`컬렉션 필드라면, 이미 내부에서 생성자복사(얕은복사)`를 통해 `기존상태값과의 연결을 끊어놓은 상태`로 내준다.**
- **그 내부의 개별요소 객체들을 set계열로 호출해봤자 새객체가 반환된다.**
- 개별객체들은 get계열로 호출해도 연결안되고 반환된다.
- **view로 보내는 `컬렉션 필드 반환 getter들은 깊은 복사`를 이미 하고 있어야한다.**
- **조작불가 + 깊은복사까지 이루어져야한다.**
- **개별요소들을 view에선 건들이면 안되기 때문**
- **암기getter**
1. **`getter정의시 return 불변객체`라면, `public열어두기 가능 + dto없어도 됨`**
- **불변의 일급컬렉션 객체 반환 -> dto없는 public getter로 제공**
- **불변객체 아니라면, `Dto로 만들어서 반환`**
2. **`getter정의시 return 컬렉션필드라면, view로 보낼 땐, 깊은 복사로 못건들게 해서 반환`**
- **불변의 일급컬렉션 `자신 내부의 컬렉션 필드 반환`**
- server사이드 반환이라면, 얕은복사 반환후, 내부에서 객체들 조작
- **view반환이라면, `깊은 복사 반환 or DTO로 반환` 후, 내부 객체들 조작안되게**
10. **Ready도 getter가 있어야하나?**
- 0개 카드 확인? 1개 카드 확인? -> **갯수만 메세지보내서 확인하면 되는데, 내부까지??**
- 선택이다
1. **Ready는 `카드 2장채워질때까지==다른상태객체 될때까지` 값을 반환안하려면 `thr`로 막아주기**
2. **보여줘도 되면 getter구현**
-
getter 추가 -> 중복필드로서 같이 움직이는 해당 상태값
이 추가된 상황에서-
최상위 인터페이스에서부터 추가
되었으며 -
구현시,
어느 구상체들도 thr로 예외없이 다 100% 다 구현
된 상태라면 -
구상체 100%구현 메서드 + 필드
로서중복제거를 위한 중간추상층
이 추가된다.
-
-
추상클래스 이름은 Finished를 포함하여 모든 구상체를 아루는 형용사인
Started(시작된 상태)
로 한다.-
State인터페이스를 구현한 추상클래스를 만든다.
-
getter만 구현하여 막아주고,
구상체 100% 동일구현이 아닌
나머지는 다 흘려보낸다 -
중복된 필드의 구현이라면
생성자 대신 public final 템플릿 setter로 정의
한다. -
하위에 있는 추상클래스Finsished, Ready, Hit는 State가 아닌
Started중간추상층을 extends
하고중복되는 getter + 필드 + 생성자
를 모두 제거한 뒤,필드를 사용하던 곳은 getter로 / 생성자로 받던 재료는 setter로
변경해야한다.-
Finished
-
Ready
-
Hit
- 자신의 필드 cards를 사용하던 곳은
부모가 물려준 중복메서드인 템플릿 getter -> cards()
를 내수용으로 사용한다. - 자신의 생성자 주입으로 상태를 받았던 것을
부모가 물려준 중복메서드의 템플릿 setter를 이용해 물려받는 -> 부모의 field를 채우고
-> 나는 getter cards()만 사용할 수 있게 한다
- 자신의 필드 cards를 사용하던 곳은
-
-
-
추상클래스로 중복필드 +
생성자까지 제거 하다보니 -> 생성자가 없으면 public생성자
상태가 되어버렸다.-
하지만,
패키지내 중간객체 클래스
or유틸메서드 클래스
의 생성자들은default와 private
으로 잠겨있어야한다. -
패키지내 시작객체
Ready를 제외한 모든 구상 상태겍체들의 보이지 않는 기본생성자를 재정의해서 default로 변경
해주자
-
-
상태객체의 인터페이스
오퍼레이터는 최소 4개+ @가 필요
하다-
trigger 메서드: draw()
-
중단 메서드: stay()
-
현재상태값 반환메서드: getter cards()
-
is특정[중간추상층]인지
확인 메서드: boolean isXXXX()-
여기서는 stay개발시 불가하여 묶여진
Finished 형용사 중간추상층
이 이미 있으니끝난 상태냐
라고 물어볼 수 있다.특정 구상체인지는 물어보지 않는다. 그놈빼고 다 false를 대답하기 때문에
->특정 구상체인지는 물어보지말고 메세지를 던진다.
-
다형성 인터페이스에 존재하는 boolean 메서드는 -> 구상체들이 구현만 해주면 알아서 개별 답변되는 메서드
다
-
-
-
State인터페이스에 특정상태(그룹, 중간추상층) 확인 메서드를 구현하고
-
Started는 건너띄고, Finished에서는 True / 나머지 구상체 Ready, Hit에서는 False를 return하자
- Finished에서 구현했다면, 추클로서 중간에 중복을 먹어 구현하였으니 final을 달아야한다
-
-
상태객체의 인터페이스
오퍼레이터는 최소 4개+ @가 필요
하다- trigger 메서드: draw()
- 중단 메서드: stay()
- 현재상태값 반환메서드: getter cards()
- is끝난상태[중간추상층]인지` 확인 메서드: boolean isXXXX()
-
현재 상태객체를 만드는 상태값Cards를 이용해서,
정보를 가진놈이 기능을 가진다
- 정보전문가패턴에 따라, 상태값으로 가능한 연산은, 가진 놈이 한다.
- cards를 가진 state객체가, cards로 계산하는 것을 한다.
-
상태별 수익계산 profit()
메서드를 State인터페이스에 구현해보자.-
만약,
추상체변수만의 구상체별 자동 구현
을 모른다면? state객체를 보유한 player 내부에서-
state.isBlackjack()
? -> 블랙잭 계산 -
state.isBust()
? -> 0 반환 -
state.isStay()
? -> Stay 계산 -
하나하나 다 확인해서 그에 따른 계산을 해줘야한다.
-
-
추상체변수의
구상체별 기능 구현
은전략패턴처럼 구현만 하면 알아서 적용
된다.-
이미 외부생성(전략패턴) or trigger메서드(상태패턴)에 의해 State state안에는 Blackjack이나 Bust나 Stay가 담겨있는 상태다
- 개별로 구현만 해주면, 알아서 작동한다.
- 대신, 알아서 계산안되어야할 놈들은 thr나 예외처리 해놔야한다.
-
이미 외부생성(전략패턴) or trigger메서드(상태패턴)에 의해 State state안에는 Blackjack이나 Bust나 Stay가 담겨있는 상태다
-
추상체 변수의
상태이용 개별구현 메서드
는 구상체를 물어보지말고 구현만 해놓으면 알아서 적용된다.- 상태값이 일반 객체였으면… 물어보고 판단했을 것인데… 추상체변수인 순간 알아서 구현됨을 생각하자.
-
-
State 인터페이스에
전략패턴과 달리, 구상체들이 가진 상태값cards
를 바탕으로 계산하는 기능인profit
을 구현하되,파라미터로 [계산마다 바뀔 수 있는 필요정보 betMoney]
를 인자로 받자-
구상체들이 상태값을 가지는 경우, 인터페이스 오퍼레이트는 상태값을 이용한 계산 기능도 추가할 수 있다.
- getter도 그랬고..
-
인터페이스에 메서드를 올리는 순간
알아서 구상체별로 개별구현되는 메서드구나
생각하자 -
계산의 대상은
이미 끝난 상태의 Finished
만 해당이 된다.-
Ready와 Hit는 예외상황으로 먼저 막아놓자.
-
-
-
구상체들에서 개별구현되니 일단은 중간추상층(Finished)는 건너띄고 구현
한다- Blackjack -> betMoney의 2배 반환
- Bust -> -1을 곱해 반환 (그만큼 손해)
- Stay -> 추가정보가 더 필요하니 계산말고 그냥 반환
-
중간추상층이 좋은 부모가 되려면,
공통점이 아예 없는 개별구현로직 조차, 개별구현훅메서드를 래핑하고 있는 public final 템플릿메소드
로 가지고 있고,서로 다른 로직은 protected abstract 훅메서드만 내보내줘야한다.
-
CompareFile(ctrl+D)로 구상체 메서드들의 공통점과 차이점을 살펴보고,
어느부분까지를 훅메서드로 뺄 것인지 생각
한다.- 상수만 서로 달라도, 해당부분을 훅 메서드로 뺄 수 있다.
-
구상체들 중
서로 다른 훅 부분에 가장 파라미터가 많이 사용되는 로직
을 가진 구상체 1개를 선택하고해당 메서드를 중간추상층으로 잘라내서 올린다
-
올린 public 템플릿메서드 내부에서
다른 부분만 내수용 메서드 추출(구상체private -> 추상체protected abstract)의 훅메서드로 만든다
- 올리고 나서 훅을 만든다면,
메서드 추출후 가시성을 private -> protected abstract로 직접 변경
해줘야한다. - 올린 템플릿메서드는 final로…
- 올리고 나서 훅을 만든다면,
-
나머지 구상체들은,
훅메서드를 구현
하면서, 기존 구현메서드의 다른 부분만 훅에 입력한 뒤, 기존 메서드는 삭제해준다. -
public 템플릿메소드는 final을 달아준다. 깜빡했으면…
-
-
중복제거 등 추상화/상속 관련작업
이 끝날 때마다, diagram을 보자- 복잡하다면, 생성자+method만 키고 보자.
-
추상화 레벨을 보고 싶다면,
바로 위의 추상클래스에 데고 usage(shift+F12)
를 통해어떤 놈들이 나를 직접 extends했는지
확인하면 된다.
-
Finished
기존 중간추상층과 추상화레벨을 맞추기
위해Ready
와Hit
구상체의 공통점과 차이점을 확인한다.-
draw, stay는 구현내용이 개별구현으로 100% 달라서 일단 유지하고
-
profit()과 isFinished()는 비슷한게 아니라 완전히 동일하므로, 추상층에 올릴 것이다.
-
-
finished에 대항하는 isFinished false를 가진
형용사 중간추상층 Running
을 만든다.-
Started를 구현하고, Ready에서 공통메서드만 중간에서 먹어준다.
-
abstract클래스며, 중복코드를 제거하는 템플릿메소드들은 final을 달아준다.
-
-
이제 추상화레벨이 맞춰졌다.
-
부가적인 처리로서,
자식들은 훅메서드만 가지도록 좋은 부모 만들기
의 일환으로- 100% 로직이 달라도 메서드명만 같다면, 100% 훅메서드인 포장 public final template으로 올리자.
-
문제는 오퍼레이터 메서드명은 유지되어야하는데, 100% 훅메서드명을 동일하게 못잡으니 어렵다.
-
draw -> drawFrom
-
stay -> stayEach
-
-
자식 구상체들은 훅만 소유
-
Finished에서도 좋은 부모를 만들기 위해 템플릿메서드로 올리고, 자식들은 훅만 소유하도록 변경해보자.
-
draw를 중간추상층의 템플릿메서드로 올리자
-
100%로직이 같아서 훅메서드를 뽑을 필요가 없었다.
-
final을 달아주는 것을 깜빡해서 달아주자.
-
-
-
그러고보니,
중간추상층 2개도 상위 추상클래스로 올릴 수 있을 것
같은데,훅을 메서드 vs 안가진 메서드
등의 차이도 있고, 카테고리 문제도 있어서 일단 여기까지만 처리한다.- 구상체(자식들)은 모두 훅메서드만 가졌다.
- 전략패턴으로 변경된다면, 전부 전략메서드만 구현하게 되는 것이다.
-
이제
상태객체를 추상체변수로 소유해서 사용하는 Player
를 다른 패키지에서 생성한다.-
상태객체는 trigger에 의해 계속 변화므로
not final 필드초기화
+set계열메서드
로 가지고 있는다. -
상태를 바꾸는 set계열이지만,
이미 정해진 종류의 상태를 가진다면 set대신 public void toggle
에 가깝다.- 필드초기화시 유일하게 열린 Ready 로 만든다.
-
-
메서드가 호출객체(자신)의 상태가 변하는 메서드(setter/toggle계열)
때,호출객체(state) 상테에 따른, 제한[호출시 예외발생코드가 존재]이 있는 action
이라면,if문으로 [예외발생 코드 상태로 진입못하게 하는] 상태condition을 확인
후 시행해야한다.- state가 blackjack, bust, stay로 업데이트되었다면, draw를 호출 시 예외가 발생한다.
-
애초에 해당 상태(Finished)가 아닐때만 호출하도록 condition을 걸고 시행한다
예외발생 코드를 짜놨찌만, 그쪽에 도달하지 못하게 막아주는 것이다.
-
만약,
매번 확인하며 자신을 업데이트
하는 작업을여러번 && 조건불만족시까지 반복
하고 싶다면 ->if condition + set계열 action의 반복문이 while
이다.-
이 때, 파라미터는
Cards의 컬렉션 뭉태기
가 아니라반복이 끝날때까지, iterator로서 1개씩 재료 제공메서드()getter를 제공해주는 객체
가 파라미터로 와야한다.-
1번일 때 파라미터가 Card라고 해서 -> Cards 나 Cards컬렉션을 생각하지말고
iterator같은 객체
를 생각하자.public 메서드의 파라미터 == 외부에서 생성되어 들어옴 -> 도메인 객체를 외부에 함부러
-> 허용하지말자my) 데이터(도메인)객체를 함부로 외부에서 생성하도록 public 메서드의 파라미터로 받지말자.
->내부에서 생성하거나, 내부에서 iter로 받자
->도메인객체의 메서드가 public으로서 외부에 공개된 것이면 상관없지만, 일단 내부생성/캡슐화 조달(iter)로 받자
-
반복이 끝날때까지, 매번 재료를 1개씩 제공해줘야한다.
-
-
-
카드를 1장씩 제공해줄 수 있는 Deck을 만들어보자.
-
Deque의 재료 컬렉션을 생성할 땐,
ArrayList
로 일단 생성하고 -
제공할 때, 얕은복사(생성자복사)시
new ArrayDeque<>( list )
의생성자 복사시 형 변환하여 deque를 제공
해주면 된다. -
이 때,
랜덤으로 섞인 것을 제공해주려면, ArrayList를 먼저 shuffle한 뒤, deque로 생성자복사해서 제공
한다 -
가변 콜렉션 -> append을 써서 생성하는 것은
stream으로 묶어서 일괄처리
가 가능하다.-
2중 for문은 stream -> map -> stream -> 기존map을 flatmap후 바깥에서 collect하면 된다.
-
-
생성자에서 쓰이는
내부context(보라색)은 필드
인데,내부 필드 초기화
는메서드 추출시 안뽑히고 내수용메서드가 되어버리니, 필드 초기화하는 = 우항값을 지역변수로 뽑아서, [필드 = 초기화]가 아닌 [지역변수 할당 = ]해서 필드초기화는 제외시키고, 위쪽만 메서드 추출하자.
-
메서드 추출시
,필드 초기화 부분은 제외
시키자! -
메서드 추출시, 다른 곳에 위임되지 않을, 내부context필드 초기화 && 재할당은 할당값(우항)만 따로 지역변수로 빼놓고, 해당 context를 제외시키고 추출한다
- 만약, 내부context를 다른쪽에 위임하는 로직이면, 내부context를 지역변수로 빼서 위쪽으로 놓고, 해당 지역변수가 파라미터로 잡히게 한다.
-
-
-
랜덤이 포함된 로직은, 보이자마자,
전략패턴적용
을 위해,메서드추출 -> 전략객체.메서드()호출 -> 전략객체에 위임 -> 전략 인터페이스 생성
으로 빼놓자.-
랜덤이 껴있는 전체로직( 객체 반환까지 )
을 1개의 메서드로 추출한다. -
위임을 위해 예비전략메서드의 인자에 new 랜덤전략객체()
를 넣어서파라미터로 추가
한다. 내부에선 사용하진 않는다.(위임하기 위해 만든 객체라서 원래 사용된 상태)-
파라미터에 전략객체가 있어야 -> 파라미터 속 1개의 객체로 위임할 수 있다.
-
-
내부 메서드가 있다면, 모두 new 전략랜덤객체를 파라미터로 추가
해준다.-
내부 메서드들 내부의
this context
도, 모두 전략객체로 교체-
this가 메서드레퍼런스에 껴있으면, 다시 람다식으로 풀어서
파라미터 추가
-
-
보라색 context가 껴있으면, 파라미터로 추출
-
-
내부메서드들부터 F6으로 위임한다
-
만약, 바깥부터 하면, 내부 내수용메서드 == Deck메서드 == Deck가 파라미터로 달린체로 위임된다
-
아주 안쪽 && 먼저 실행되는 내수용 메서드들부터 위임하자
-
위임된 내수용메서드들은 수동으로private으로 바꿔주자.(옮길때는 위임해준 쪽으로 넘겨줘야해서 public이 자동으로 걸린다.)
-
-
-
이제, 특정 전략객체가 아닌,
전략인터페이스를 파라미터를 통해 외부에서 주입
받아서 소유하도록 하기 위해,구상체로부터 전략인터페이스를 만들어 올린다
.-
구상클래스에서 @Override -> extract interface -> 전략인터페이스 생성 -> 전략메서드만 추출
-
구상체 사용처(Deck)에서 파라미터 추출 -> 생성자로는 전략인터페이스가 들어오도록 수정
-
-
-
이제 Deck에서는 랜덤Deque카드들을 소유한 상태이므로 Player에게 pop으로 1개씩 제공해준다.
-
Player는 while문으로 상태확인하면서, Deck으로부터 1개씩 재료를 iterator처럼 제공받는다.
-
-
하나의 기능만 뺀 전략패턴은 람다식을 편하게 사용하도록 애너테이션을 붙여준다.
-
RandomCardGenerator는 List로 종류별로 만든 카드 List -> Shuffle후 -> Deque로 반환해주는데,
Deque의 재료 List는 1번만 생성해놓고, 필요시 shuffle후 생성자복사만 매번 해주면 된다.
- 즉, 카드종류별 List는
static 필드에 미리 1번만 생성해놓으면 된다.
- 즉, 카드종류별 List는
-
이제 Random로직이 보여서 묶어서 만든 전략패턴의
수동전략 생성 or 람다식 대입
을 만들어보자.- 수동전략은 사실상 Test에서 밖에 안쓰이므로
도메인에 전략객체 생성을 안한다
- 람다식으로 바로 대입이 불가능하면,
수동전략 클래스를 testutil에 추가
해준다.
- 수동전략은 사실상 Test에서 밖에 안쓰이므로
-
전략객체가 주입되는 객체를 생성하고,
전략객체 주입부
에가상인자 람다식으로 [전략메서드 대신구현]
이 가능한지 보자.- 확인해보니 , Deck의 생성자에서 쓰고 있다 -> 테스트에서 new Deck() 을 만들자.
-
람다식으로
전략메서드 로직을 구현하여 시그니쳐에 맞는 응답
을 해줄 수 있는지 확인하자.- 전략메서드는
Deque<Card>
인new ArrayDeque<Card>();
를 채워서 응답해주면 된다.
- 전략메서드는
-
람다식 내에서 수동으로 채워주기엔 무리가 있으니
위쪽에서 지역변수로 채워놓고, 해당 지역변수를 넣어줘도 된다.
-
카드2, 5를 수동으로 채워서 deck을 pop해보면, 뒤쪽 5가 나온다
-
수동으로 전략메서드 시그니쳐 응답값 구현
을 반복해서 사용할 예정이라면메서드 추출후, Fixed수동전략객체Class에 위임
하여 재활용한다.-
이 때,
수동으로 넣어주는 값들을 변수로 빼서 사용할 수 있다.
- 랜덤카드생성은 정해진 수의 카드만 가능
-
수동카드생성은,
파라미터로 원하는 요소들로 짧게 구성 가능
-
원하는 수만큼 받을 수 있게
2번째 파라미터부터를 가변배열로 받아서 처리
한다
-
-
이 때,
테스트용 수동 전략이 적용된 객체
생성까지를하나의 fixture생성
으로 보고 묶을 수 있다. -
캐싱객체 or 불변Vo객체의 Fixture 모음이 아니라
1개의 FixtureGenerator
로서testutil패키지지 > FixtureGenerator클래스를 만들고, 생성 유틸메서드로 추출
하여 재활용할 수 있다.-
testutil패키지 아래
FixtureGenerator
클래스를 만든다 -
private메서드를 유틸메서드(Fixture정팩매)로서
public static
을 붙인 뒤위임
한다
-
-
player는 카드1장 받으면 Ready이 되어야한다.
-
하지만, 최초의 Ready상태에서 Started에 선언된 공통필드가
null
상태라서 getter호출시 에러가 난다.
-
-
교훈)
not final + setter계열 조합 for좋은부모
는 null참조가 될 수 있으니반드시 초기값 필드초기화를 해주자
-
2장이상 draw하면, Ready가 아닌 상태가 된다.
-
Player가 Deck을 전달받으면 finished가 될때가지 draw로 뽑아간다.
- 이럴 경우,
hit상태도 !finished라서 계속 반복하여 stay를 못한다
- deck을 받아도 finished까지 반복이 아닌 1장만 받도록 수정해보자.
-
만약, ! state.isReady()가 존재했다면… 연속해서 Ready아닐때까지 받으면 좋으련만..
-
이 draw는 Ready를 벗어나기 위한 것으로 1번밖에 안쓰인다.
- draw는 범용으로 finished전까지 호출되어야하는 것이 맞다
-
그러나, stay호출을 무시 ready -> hit -> finished로 가기엔 finished까지 가기엔..
- stay상태는 hit -> stay()메서드호출만 가능한 finished상태임.
-
isReady는 isFinished(끝난후 호출가능한 메서드 ex> profit 등이 있는 중요한상태)와 달리, 오퍼레이터로 작성하기엔.. 무리가 있다.
- Ready빼고 finisehd그룹 -> 공통 false / Hit -> false로 2개만 주면 되긴 하는데..
-
이 draw는 Ready를 벗어나기 위한 것으로 1번밖에 안쓰인다.
- 이럴 경우,