📜 제목으로 보기

  • 랜덤을 셔플로 구현하여 데이터를 제공해주는 Deck에서, 랜덤부분을 전략패턴으로 만들고, 외부에서 [직접 반환되는 객체를 만들어놓고 -> 최종 객체만 람다식에 넣어] 제공함.

블랙잭 상태 패턴 구현

  1. 컴퓨터내부에서 로직의 시작인 card 2개가 주어진다면, 정제된input이라 생각하고 카드부터 만든다.

    • 제한된 종류의 값 = 상수묶음은 enum으로 만들어서 카드를 만든다.
    • 테스트메서드명은 인자 -> 1case 가 결정한다.

    image-20220803141223263

  2. 4 x 13 종류의 객체는 캐싱이 가능하다.

    • 캐싱을 적용하려면 일단 생성자 인자 그대로 정팩메로 만들고, 기본생성자는 private해준다

    eb9d9669-3c47-4bc2-a036-e045e7bae549

    image-20220803141533284

  3. 원래는 기본생성자의 1개 인자에 대해 hashMap<생성자인자, 객체>형으로 static CACHE map을 만들어야하지만, 파라미터가 2개일경우, 1개로 합친 Key를 만들어야한다.

    • 2개이상의 정보를 묶은 class를 만들어도 되지만, string을 이용해서 합쳐 1개의 key로 만들어 <String, 객체>형을 많이 사용한다.
    • 캐쉬의 기본공식은 꺼내서 없으면 put해주고, return get을 반환이다.

    303bdea4-3f7f-401f-8ced-e537eb227a77

    image-20220803142249589

  4. 테스트메서드명도 함수를 from or of의 정펙메로 바꿔주고, isSameAs로 테스트한다.

    image-20220803142749590

  5. java의 기능으로서 없으면 key에 대한 value를 생성해주고 있으면 반환하는 computeIfAbsent를 사용해서 리팩토링한다.

    • 또한 캐싱될 객체의 수를 알고 있으면 미리 넣어줘도 된다.

    e488a62d-24bf-4ab9-8f50-1dc5ed300d1a

    image-20220803143606805

  6. CACHE대상객체는, 거의 값객체기 때문에, 값으로 비교하기 위해 eq/hC오버라이딩도 해준다.

  7. 이제 가진 2장의 카드정해진 종류의 행위마다 변화하는 상태 판단하면서, Game을 풀어 가야한다.

    • 카드2장 -> if 상태판단 -> 상태에 따른 행위 -> 다른 상태
    • if 상태에 따른 행위의 결과가 다른 상태가 나온다면, 상태패턴을 고려한다
    • 2장의 받은 상태에서
      • if 21 -> 블랙잭 상태 -> stop
      • if 21미만 -> hit상태 -> stay or hit지속
    • 가장 만만하게 응답될 상태의 input부터 case를 만들고 -> 테스트메서드명으로 시작하면 된다.
      • hit가 output상태로 나오는 input을 넣어주자.
  8. Service가 아닌 만들어낸 input부터 시작하는 Game클래스를 만들고, 서비스메서드처럼 static으로 start()를 만들고 재료를 넣어주자. image-20220803172330589

    • input으로 hit대상이되는 카드2장을 인자를 넣어주고, 응닶값이 hit상태이어야한다.

    c67370cb-5be1-4ac1-8c1a-ad972ed07cfc

    image-20220803172709657

    image-20220803173533986

  9. 다음은 Game.start()가 blackjack상태를 응답하도록 작성해보자.

    • 테스트에 인자를 blackjack으로 넣어주고

      image-20220803174548392

    • blackjack시 ACE가 11로 사용될줄알아야한다. enum에 매핑된 값이 필요함을 인지한다.

      image-20220803174605022

    • enum에 값이 매핑되는 순간부터 바로 getter를 작성해주면 된다.

      • ACE는 1or11을 가질 수 있으나 규칙상 상한이 21점으로 제한되어있다면, 11을default시 2장을 아예 가질 수 없기 때문에, default를 작은 수로 주고 보정한다

        image-20220803175357571

      image-20220803174635793

    • card입장에서는 enum getter -> enum point getter 2번을 거쳐야하므로, getter를 래핑해서 만들어준다.

      image-20220803180048429

      image-20220803180056251

  10. 2장의 카드 중에, ACE가 포함되어 있을 경우 && [11로 쓸 때, 합 21] -> [1로 쓰면 합 11]일 경우만, blackjack이 되도록 처리해줘야한다.

    • 2개의 카드 중에 ace가 있는지 확인한다.

      • 일급컬렉션이 아닌 상태에서 동일형 객체 일괄처리는 List.of()나 Stream.of()를 사용해서 묶어서 처리한다.

      • 있는지 없는지 판단 filter + count가 아니라anyMatch( -> 개별요소판단 )로 판단한다.

        98305185-6d82-4ef3-9790-2d8f12751669

        image-20220803223426698

    • ace를 가졌다면 && 2장의 이 11이하인지 확인한다

      • 11이하이면, ace는 10으로 쓰일 수 있다.
      • 11이면, ace를 가진 경우 blackjack이다.

      image-20220803223757989

    • 즉, ace를 작은값로 유지하고, 기준값을 큰값일때로 잡아서 처리하면 된다.

      • ace(1) + 10 -> 11이면 21로 블랙잭
      • ace(1) + x -> 11미만이면, ace(11) + x
      • ace(1) + 11 -> 12이상이면, 교환불가
      • ace가 2장있다면? 변환될 수 있는 ace는 한장이다. 2장 변환시 이미 22가 되어버림

      image-20220803224512177

  11. 테스트를 통과했다면, 리팩토링한다.

    1. 일급컬렉션이 아닌 같은형 객체 일괄처리를 위해 List.of()로 묶었지만, 같은형끼리의 단순집계또한 List.of()로 묶어서 누적연산으로 처리가능하다.

      • 같은 context에서 일괄처리같은형 끼리의 연산이 뭉쳐있다면, List.of()나 Stream.of()로 묶어주는 것은 공통코드가 된다. 파라미터 추출로 각각을 빼낸뒤, 1개의 변수로 대체할 수 있다.

      c99a733b-5da6-459c-afda-cf65c89c814e

      image-20220803225702034

    2. 같은형끼리의 단순연산 or 일괄처리로 인해 2개이상의 같은형을 List.of로 묶었다면, List.of()로 묶는 곳에서 일급컬렉션을 고려한다.

      • Stream.of()는 List.of()로 바꿔서 일급컬렉션의 생성자 인자로 올리자.

      • 이후 인자없는 생성장에서 빈컬렉션으로 초기화하여, add가능한 일급컬렉션으로 수정하자.

        image-20220803230332195

        bd76e09c-c932-4314-8299-2d915b9bc787

        image-20220803230845620

      • 인자없는 생성자는 부생성자이므로 빈컬렉션 초기화를 this()에 넣어서 초기화해주자

        • add시 새객체 반환시, 얕은복사 안해줘도 add전 객체는 버려져서 오염될 일이 없을 것 같아서 빼줬다.

        1733ea67-d615-4ce6-9ac7-aafe4801d9a3

        image-20220803231130552

  12. 책임위임을 위임하려면, 일단 현재context상의 메서드의 파라미터에 걸려있어야 하며, static메서드라면, static키워드를 삭제하고 위임해야한다.

    • getter를 쓰더라도 파라미터로 들어가서 완성해놓자.
    • static메서드라면, static키워드를 지운 뒤 f6으로 위임해야한다
    • getter를 썼다면, 위임된 객체에서는 내수용getter가 사용되고 있으니 지우고, 필드로 바꾸자.

    ac7f2bd5-ca83-4487-bcd0-c1065dc82c97

    image-20220803234008203

  13. 또다른 리팩토링으로서 조건식에 하나의 도메인에 대한 메서드호출이 나열되어있다면, 메서드 추출시 파라미터에 1개만 도메인만 걸리며, 이 또한 묶어서 책임을 위임할 수 있다.

    • 역시 static안이라면, static을 지우고 위임한다.

      image-20220803234519904

      image-20220803234556959

  14. 상태패턴 적용을 위해 state라는 추상체 인터페이스를 만들고, 응답형을 Object가 아닌 추상체로 주면 -> 알아서 응답되는 객체들을 구현하라고 intellij가 알려준다.

    280a5884-db1a-4fb6-8d73-50c00726bcde

    image-20220804114048025

  15. 테스트코드의 응답형에 따른 변수형도 변경해준다. image-20220804114302886

  16. 추상체를 만드는 순간부터 패키지를 분리하여, 패키지폴더 대상 다이어그램으로 의존성을 확인하자.

    • 이후, 구상체만의 메서드 개발후 올리기전 다이어그램 + CompareFile하자

    image-20220804114547248

  17. 상태패턴을 도입했으면, 응답값으로 나온 현재 상태를 바탕으로 추상체에 있는 [다음상태로 갈 수 있는 인자]를 받아 [다음상태로가는 메서드]를 정의해줘야한다. 에러호출로 종료되는 제일 쉬운 현재상태(blackjack)부터 [다음 상태로 넘어가는 메서드]를 만들자

    • 테스트메서드에 작성한 응답된 상태
      • Hit
      • Blackjack -> draw즉시 에러 호출로 종료라서 더 쉽다.

    image-20220804115950502

  18. 이 때, 특정 쉬운 구현체부터 메서드 작성이라면, 공통메서드일지 모르니, 오퍼레이터로 만들지말고 다운캐스팅해서, 구현체만의 메서드로 만든 뒤, 공통이면, @Override해서 올린다.

    • 응답값을 받을 때 (특정구현체) 다운캐스팅을 하고, 메서드를 작성한다

    7b4e31c2-60ee-4cd3-976e-aedf73a30026

    image-20220804120914397

  19. 특정구현체의 메서드이며, thr 던질 메서드라도 미래에 공통으로 사용될 예비오퍼레이터라면, 응답형을 지정한뒤 던진다.

    • 게임종료는 thr로 한다.

    image-20220804121345849

  20. 다운캐스팅된 구상체만의 메서드 개발이 완료되면, 다이어그램을 확인해서, 올려도 되는지 판단한다.

    • 다이어그램 + CompareFile을 펼쳐 모든 구상체가 호출해도 되는 메서드인지 확인한다.
      • 다이어그램 단축키 : ctrl + alt + shift + U
      • compareFile : 구상체들만 선택후 ctrl + D
    • 올릴 거면, 다운캐스팅했떤 로직을 삭제하고, 다른 구상체들도 구현한다.

    5f9ff154-0934-4173-a368-295ad75eadba

  21. Override + pull members up으로 오퍼레이터로 올린 뒤, 테스트상 다운캐스팅한 로직은 삭제해준다

    ae3803d6-7046-4488-a724-eb9ba30ff1bc

    image-20220804125036585

  22. blackjack -> 게임종료(thr)는 끝났다. 이제 hit상태에서 -> draw or stay를 할 수 있다.

    • 일단 draw부터 만든다.
    • hit상태에서는
      1. 합 20이하라면, hit상태
      2. 합21이라면, blackjack상태
      3. 합 21초과라면, bust상태이다
    • 테스트에서는 case마다 인자로 넣어줘서 차근차근 개발하며, 제일 쉬운 것(hit)부터 응답하게 만들어나가면 된다.
  23. hit응답상태에서 합 20이하가 되는 인자를 넣어줘 다시 히트상태를 응답하도록 case를 만든다.

    • 첫 case만 만들 땐, 빠르게 return이라 연산식이 없다. 하지만,,,

    32a9fc9b-72a3-4f55-b1f6-bf7d929e9ae8

    image-20220804125940467

    • 아직까지는 hit만 빠르게 반환하도록 만든다.

      image-20220804130150609

      image-20220804125843507

  24. 이제 hit에서 2번째 쉬운 상태응답case인, hit -> bust로 간다.

    • 테스트를 작성하고

    image-20220804131116751

    • Bust 클래스를 만들고, State구현한 뒤, hit의 draw()의 내부 로직을 작성해야하는데..

      image-20220804131254370

    • Hit의 트리거메서드인 draw의 2번째 case Bust를 판단하려면, 기존 카드정보를 모두 상태값으로 쥐고 있고 vs 메서드 인자로 넘어온 정보를 통해 상태업데이트 후 새객체반환해야한다.

      image-20220804131617383

  25. 최초의 상태객체를 만들어내는 Game.start()에서 현재정보를 생성자의 인자로 넘겨줘 상태값으로 가지고 있어야한다.

    image-20220804131920221

    • trigger + 정보를 판단하려면, 상태객체는 생성시부터 이미 정보를 상태값으로 가지고 있도록해야한다.

    • 최초로 상태객체가 만들어지는 곳에서 현재 상태를 결정하는 정보를 생성자 주입해서 물고 있자.

      d3c82900-8efc-4652-885c-f1b6844206a3

      image-20220804132506391

    • 기존에 생성자 없이 사용하던 객체에, 상태값이 추가되어 생성자가 추가될 경우, 부생성자로 빈값할당으로 초기화해주는 기본생성자를 추가해서 기존코드가 망가지게 않게 한다

      45c77192-814c-4c49-9d5e-a763f5ca0adb

      image-20220804132909980

      image-20220804132919754

      image-20220804133122081

    • 또한, trigger메서드내에서는 업데이트된 상태값으로 새 상태객체를 생성해서 반환해줘야한다.

      • 상태값도 포장된 일급컬렉션이라, cards + card를 처리해야한다.

      image-20220804132354817

  26. 같은형의 일급vs단일라면 메세지를 보내서 처리해야한다. 주로 add가 쓰일 것이다.

    • add를 만든다면, list + add로 인해 상태변화된 불변일급컬렉션을 반환해줘야한다.
    • 이 때, 기존 상태값(컬렉션 필드)를 얕은복사후 add해야한다.
    • 컬렉션 파라미터의 사전검증으로서 일급컬렉션에서 add하여 새로운 컬렉션 상태를 만들어, 새로운 일급컬렉션 객체를 불변하게 반환할 때는, 기존상태값을 얕은복사해서 연관성을 떼어내야한다

    679b4781-0333-4a55-8484-3cef333d5a02

    image-20220804154848612

  27. 상태필드가 객체이상, 일급컬렉션인 경우, 현재 구체적인 값의 상태를 물어볼땐 getter대신 메세지를 보낸다

    • 상태필드가 객체이상, 일급컬렉션인 경우, 현재 구체적인 값의 상태를 물어볼땐 getter대신 메세지를 보내서 값으로만 응답받는다.
      • 이미 가진 카드들의 합을 묶어서 연산하도록 프로그래밍 되어있으니, 갯수가 늘어나도 합 연산은 그대로 유지되고, 그 메서드를 내수용으로 사용하면 된다.

    image-20220804155811937

    • 상태값이 객체이상이라면, 메서드를 보내서 현재상태를 물어본다

      2659722a-0b81-42b2-baf2-d05d89a2f64e

      image-20220804160524197

  28. 현재 진척도

    • hit or blackjack
      • blackjack -> 뽑을 시 예외발생해서 종료
      • hit
        • hit
        • bust -> 뽑을 시 예외발생해서 종료 « 먼저 처리해주자
        • blackjack?
  29. 종료상태객체는 trigger메서드 호출시 예외발생으로 종료까지 마무리해줘야한다.

    • Blackjack상태객체, Bust상태객체 (앞으로 Stay도 종료상태일 것임)

    image-20220804161343735

    • 참조변수 재할당시 람다캡처리 문제가 발생한다. 체이닝가능한 메서드는 체이닝해주자.

      image-20220804161817136

      image-20220804161614014

  30. setNext, add와 같이 같은형 객체를 반환하는 메서드들뿐만 아니라 같은카테고리인 추상체를 반환하는 메서드들도 체이닝 메서드이다.

    • 객체반환메서드는 체이닝을 생각하자.
    • 이 때, static메서드로서 게임출발을 담당했던 메서드도 State를 반환하는 메서드이므로 체이닝 가능하다

    image-20220804162044271

  31. 2장인데, ace를 안 가진상태에서도 블랙잭이 될 수 있다. hit -> blackjack을 개발해보자.

    • hit or blackjack
      • blackjack -> 뽑을 시 예외발생해서 종료
      • hit
        • hit
        • bust -> 뽑을 시 예외발생해서 종료
        • blackjack -> ace를 가졋다면 합 11 , ace가 없다면 합21시 만족

    image-20220804163356345

    image-20220804163338000

    image-20220804221126471

  32. 핵심로직을 시작하는 카드2장input -> 상태객체 output하던 스태틱 클래스도 Ready 상태의 상태객체다.

    1. Game -> Ready 수정

      • 테스트의 Ready.start() -> 찾아바꾸기로 new Ready().start()로 수정하자.

        e17fed9b-1609-49f1-a76d-868c057590ee

      image-20220804221313986

    2. State인터페이스 구현start보다 구현메서드draw()를 가장 위로

      • 상태를 구현한 구현객체라는 의도를 명확히 하기 위해서는 구상메서드를 가장 위로 올린다.

      image-20220804221725083

      • 상태객체 패키지로 이동시킨다.

        image-20220804222040550

  33. 서비스 메서드 == 스태틱 메서드 like 유틸메서드에서 -> 객체가 되었다면

    • 상태값으로 카드 2장을 받는다.

    • 상태값을 만드는 input들은 다 생성자로 들어와야한다.

      • 메서드로 상태값 정보가 들어온다면, setter다. 지양해야한다.
    • 서비스의 유틸메서드 input -> 생성자로 주입 된다

    • 서비스의 유틸메서드내 로직을 거친 뒤 1개의 output응답값(상태객체) -> 필드로 가진다.

      image-20220804223725402

  34. start의 카드2장의 input은 생성자로 주입되고, 1개의 output인 상태객체는 필드가 되어 상태객체를 초기화해서 최초로 가지는 상태 포장객체가 Ready가 된다.

    • 즉, 서비스메서드는 나중에 첫 상태객체를 초기화해서 사용하는 포장객체가 되는 것이다.

    5272c2d7-0faf-41ed-bfc5-3f201631f616

    image-20220804224629295

  35. 이렇게 Cards가 아닌 다른 상태객체를 가지게 되면, 상태객체가 아니라 상태객체를 사용하는 객체가 된다.

    • 하지만, Ready라는 상태객체도 상태를 사용할 객체가 State state의 초기값으로 가져야할 객체로서 엄연히 존재해야하며 다른 상태객체들처럼 draw()를 구현하여 다른 상태로 넘어가야 한다
  36. 정리하면

    1. Ready도 다른 상태객체들과 동일 상태값 필드(Cards)를 가지며 생성자로는 아무것도 주입 안된 빈카드로 초기화하는 기본 생성자로 만들어져야한다.

    2. Ready도 .start()로 다른 객체로 넘어가는 것이 아닌 다른 상태객체들의 메서드처럼 Trigger메서드(draw())로부터 setter정보를 받아 상태값업데이트 이후 상태값을 가지고 판단하여 다른 상태로 넘어가도록 수정한다.

      • Ready는 카드를 2장을 받는다. -> 다른상태객체처럼 draw로 1장씩 받도록 정의하고 외부에서 2번을 호출하던지 다른 파라미터로서 받도록 오버로딩메서드로 정의해준다.
  37. 빈생성자 -> 빈 상태값으로 생서될 수 있도록 생성자를 수정하고, 다음 상태객체로 넘어가는 로직을 draw로 옮긴다.

    8821434b-59bf-44fa-857c-0bf99997e8f8

    image-20220804230734646

  38. 문제는 Ready는 1장이 아니라 2장을 다 받은 상태에서 판단이 이루어져야하는데, 1장만 받고 hit로 바로 가고 있다.

    image-20220804230943424

  39. Ready가 2장을 받은 상탠지 확인하는 것은 Ready의 상태값인 cards로 판단해야한다.

    • 객체이상이므로 메세지를 던져 물어본다. 너 2장 받았냐고, 아직 2장미만의 Ready상태냐고
    • 1장 받고 상태값이 바뀌면, 새객체를 반환해야줘야하므로 다시 new Ready(바뀐cards)로 응답한다.
      • cards로도 만들어져야하므로 생성자가 추가되고, 기존 생성자는 부생성자가 된다.

    81ab59df-4c25-4618-99ca-509c250ba0e

    image-20220804233720485

    • 밑에 로직도 업데이트된 상태값으로 생성하도록 수정한다.

      image-20220804234229117

  40. test에선 다 start(카드, 2장)으로 작성된 상태다.

    • 코드 수정은 기존코드start를 복사해서 수정draw하고 나서 지운다.

    648a43fa-9915-4db6-8955-27b213f1bc8e

    image-20220804235357245

    • 서비스 or 핵심로직 통합테스트가 Ready라는 1개 상태객체 테스트로 바꼈다.

      • 개별 상태객체에 해당하는 코드들은 각자의 테스트로 나누어서 옮긴다.

      • 비록 Ready에서 시작하지만 일단 옮겨간다.

      647523ba-25d4-4853-9ece-f32ec5ff414e

  41. Ready부터 시작하는 테스트를, 해당객체부터 시작하도록 수정한다.

    • Ready는 인자없는 생성자에서 draw()2번했지만
    • 특정상태 객체는 cards를 상태값으로 주고 시작하도록 변경하면 된다.

    image-20220805002505575

  42. 파라미터 변경한 생성자 추가해주기 -> 파라미터 추가는 해당 파라미터로 생성자 추가한 뒤 오버로딩으로 처리한다고 했다.

    • client가 편해야한다.

    • Cards를 만드려면 List.of() 로 묵어줘야하니, 내부에서 묶어주고 가변인자로 주도록 변경해보자.

      • 기존 image-20220805002739625

      • 인자입력시 List.of() -> 가변배열로 입력하도록 변경한다.

        172a6aed-0912-45a7-a0d6-ff892d0afe2f

        image-20220805003122391

        image-20220805003145189

  43. 상태변화가 없는 캐싱 객체는 싱글톤이라, testutil패키지 > Fixtures클래스안에 상수객체로 만들어 써도 된다.

    • test루트에서 testutil 패키지를 만들고 내부 Fixtures를 만들자

      bffe3520-7e82-4591-8991-e8338e197072

      image-20220805123413269

  44. Fixtures에 쓸려면 어디서든 불러도 같은 객체가 생성되도록 캐싱==싱글톤이 보장되어야한다.

    • isSameAs로 확인해야한다.
    • 만약, 캐싱객체가 아니더라도, eq/hC를 재정의하여 값이 같으면 같은 객체가 되는 값객체가 되어야한다. 이 땐, isEqualTo로 정의한다.

    image-20220805124337165

  45. **싱글톤 캐싱객체 or VO라서, 생성해서 쓰면 무조건 같은 객체가 보장되었다면, 테스트에서 쓰인 객체들의 상수추출 -> Fixture로 옮겨서 재활용할 수 있다. **

    • 상수가 되면 편하게 생성안하고 편하게 쓰면 된다.
    • 한글로 Fixture를 생성해서 쓰자
    • 상수 추출후, Fixture로 옮기기전에 미리 다 상수로 바꿔놓으면 더 쉽다(ctrl+H)

    7ed764cd-8939-486c-899a-0c81689cf785

    image-20220805125353849

  46. 이제 빈 생성자로 빈 상태값으로 시작하는 Ready의 [시작상태객체]가 완성되었다.

    image-20220805130924167

  47. 시작객체가 있다면, 같은 패키지로 몰아서 시작객체public을 제외한 나머지들은 default가시성을 가지도록 해서, 외부에서 생성안되도록 한다면, 사전검증을 안해도 된다.

    • 만약, 생성자가 기본생성자라서 정의를 안해줬다면, 재정의 후 가시성변경해줘야한다.
      • 대표적인 예가 유틸클래스(상태값없이 input->output static메서드만 가지는)는 생성자를 private가시성으로 변경해주는 것이다.
      • 생성자가 없는 클래스 -> public으로 열여있으며, 재료를 바탕으로 상태값을 만들지 않는 클래스
    • client가 잘못입력할 수도 있기 때문에 중간에 생성되는 상태객체들외부에서 생성못하도록 가시성으로 막아주기

    9c833c96-8fe1-4e44-965e-681bbf771ffa

    image-20220805133739069

  48. 상태객체는 trigger메서드(draw)외에 중도 stop메서드와 stop상태를 가질 수 있다.

    • ready + trigger

      • blackjack
      • hit + trigger
        • bust
        • blackjack : 블랙잭은 2장일때만 가능이다.
        • hit
      • hit + stop : 중도stop 메서드
        • stay : 중도 stop상태객체 반환
    • stop메서드와 stop상태객체 2개를 다 만들어준다.

      image-20220805175144554

  49. hit상태에서 stay를 호출하도록 테스트를 짠다. image-20220805175856032

  50. 메서드가 반환해야할 상태객체부터 만들고, State 추상체상태의 hit에서 stay()를 만들면, 오퍼레이터로 등록된다.

    image-20220805180022327

  51. 각 구현체는 외부에서는 추상체변수에서 다형성으로 사용되니, 특정 구상체(hit)에서 빨간줄 메서드로 만들면, 추상체인 인터페이스의 오퍼레이터로 올라간다.

    • 일단 인터페이스에 생성하고 -> 해당 구현체만 일단 구현해보자.

      363f9945-4c87-4673-a7e5-4f418b00b0bb

      image-20220805180538834

      image-20220805180525972

  52. 특정 구상체에만 사용되는 메서드가 등장했다.

    • 상속관계면, 조합관계로 바꾼다.

    • 이미 인터페이스를 구현하는 조합관계의 구상체였다면

      1. 일단 인터페이스에 올리고, 특정구상체만 자기메서드를 로직을 구현한다.
      2. 마저 테스트를 진행해야하니 나머지 구상체들은 로직 없이 구현만 해준다
      3. 이후 해당메서드 테스트가 끝나면, 특정구상체 외 사용하지 않는 구상체들에게 내리기 전중간에서 막아주는 추상클래스로 만들어지는 중간추상층을 만들어준다.
    • 일단 모든 구상체들도 구현은 하고 특정 메서드의 테스트를 진행한다.

      cc2f7b9b-ca37-4345-9a92-387415551210

      image-20220805181221407

  53. 사용하지 않는 메서드를 억지구현한 코드가 중복이라서 해당하지 않는 구상체들만 묶은 중간추상층(추상클래스)를 도입하고, 거기서 구현해준다.

    • 중간추상층은 구상체로 내려가는 메서드를, 중간에 구현하여 중복되는 코드를 가운데서 막아준다.

      • 이 때, 중간추상층은 카테고리로서, Ready는 같이 묶기엔 의미가 다르다

      image-20220805181543133

    1. 일단 인터페이스를 구현하는 중간추상층 추상클래스를 형용사 상태네이밍해서 만들고
      • stay할 수 없는 것들은 이미 끝난 Finished라고 하고
      • Ready는 논외로 성격이 달라, 놓아두고
      • Hit도 구현하므로 놓아둔다
    2. 해당하는 구상체들은 추상클래스를 상속한다
    3. 사용하지 않는 메서드는 thr IllegalState로 처리한다.
  54. State를 구현한 추상클래스Finished를 만들고

    1. abstract를 달고, 2개의 오퍼레이터 모두 구현해주되
    2. 먹어줄 것(stay -> 사용안됨)만 남기고, 개별구현할 것들은@Overide 구현 메서드들을 자식들에게 다 내려보낸다

    ada195af-063e-4373-a796-eaceee8ea527

    image-20220805183950787

    1. 하위 구상체들은 인터페이스 impl이 아닌 중간추상층인 Finished를 extends한다

      image-20220805184042552

  55. 추상클래스 도입시 중간에 먹어줘서 공짜로 물려주는 것들은 final을 달아서 자시이 수정 못하게 막아야한다

    image-20220805184221982

    • 구상체가 도입되고, 추상체도 추가되었으니 다이어그램을 본다.

      image-20220805184548517

    • 추상클래스 도입후 눈꽃모양의 final을 확인하자.

      image-20220805185002337

  56. 모든 구상체에 대해 stay()메서드가 추가되었으니 메서드 테스트를 추가한다.

    • blackjack, bust, stay -> finished -> stay호출시 예외
    • ready -> stay호출 시 예외

    image-20220806125452720

    • Stay 도메인객체가 추가되었으니, 생성 및 메서드 테스트를 해야한다.
  57. 상태패턴의 상태객체반환 with trigger되는 결국엔 다른 객체의 상태값으로 포장객체들은 view에 넘기기 위해 getter를 무조건 가진다

    • trigger 등 set계열 메서드들 개발이 완료되면 getter도 개발해야한다.

    • 원래 추상클래스의 중복필드처리부모필드는 생성자 정의없이 -> setter만든 뒤, setter주입되어서 자식이 super를 못쓰게 해야하는데..

  58. 복잡한 구조에서 getter개발하기

    • 현재는 복잡한 구조로 되어있기 때문에, 메서드 개발을 1개의 구현체에서부터 올리기를 하진 못한다. 최상위 인터페이스State에서 정의후 하나씩 내려오기해야한다.

    image-20220806133501868

    1. 최상위 인터페이스에 getter를 정의하되 값의 반환이 아닐 때는 .get필드()대신 .객체()형식으로 정의해본다.

      • 인터페이스에서는 접근제한자가 public이 default이므로 두고 반환형을 상태값 Cards로 주면서, 메서드명도 cards()의 필드명으로 만들자

      09e19aec-4613-43ef-813f-ecf2fee9b6c8

      image-20220806135501987

    2. 개별 구상체(Ready, Hit)는 바로 구현하되, 중간추상층 Finished에서는 해당오퍼레이터가 중복인지 확인한다.

      • getter오퍼레이터의 구현은 getter구현 전에 필드값이 존재해야하는데

        • 중간추상층의 부모가 없다면, 바로 자신의 필드를 반환해주면 된다.

        • Ready / Hit 구상체

        48e74868-8edf-456e-99b8-e8ba38a8c9b4

        image-20220806135930790

      • **중간추상층에서 먹어준다면, [좋은 부모로서 생성자없이 setter로 -> private필드올릴 준비]를 해야한다 **

        image-20220806140117699

    3. 3개 구상체에 대해 공통getter라고 판단이 들었으면, final 템플릿메서드로서 중간에서 먹어준다

      c9ed91ed-d0dd-418f-8466-47dac1b52d4d

      image-20220806140435264

      • 추상클래스는 무조건 public final or protected abstract다

        image-20220806140531052

    4. 문제는, getter로 반환될 필드를 부모인 중간추상층에 올리는 순간, 부모의 생성자가 정의되어 자식이 super를 쓰는 불상사가 생길 수 있다.

      image-20220806140408780

    5. 부모는 해당필드에 대해 생성자가 아닌 public final setter로 객체를 초기화시키며

      1. 해당 부모의 구상체 자식들은 안보이지만 setter를 이용해서 해당 필드를 채워야한다

        1. 맞는지 모르겠지만, setter로 인해 상태값 변화했으면, 변화된 객체를 반환해줘서 체이닝가능하게 한다

          • 예시

            image-20220806141343001

            image-20220806141236714

        ad86e7b6-b7a6-4c9e-b66b-9a45db6744ba

        image-20220806141431436

      2. 기존에 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구현**
  1. getter 추가 -> 중복필드로서 같이 움직이는 해당 상태값이 추가된 상황에서

    1. 최상위 인터페이스에서부터 추가되었으며

    2. 구현시, 어느 구상체들도 thr로 예외없이 다 100% 다 구현된 상태라면

    3. 구상체 100%구현 메서드 + 필드로서 중복제거를 위한 중간추상층이 추가된다.

      image-20220806171853571

  2. 추상클래스 이름은 Finished를 포함하여 모든 구상체를 아루는 형용사인 Started(시작된 상태)로 한다.

    1. State인터페이스를 구현한 추상클래스를 만든다.

    2. getter만 구현하여 막아주고, 구상체 100% 동일구현이 아닌 나머지는 다 흘려보낸다

    3. 중복된 필드의 구현이라면 생성자 대신 public final 템플릿 setter로 정의한다.

      463ad1ff-3be7-4fee-a21a-29df67773e8e

      image-20220806172328738

    4. 하위에 있는 추상클래스Finsished, Ready, Hit는 State가 아닌 Started중간추상층을 extends하고 중복되는 getter + 필드 + 생성자를 모두 제거한 뒤, 필드를 사용하던 곳은 getter로 / 생성자로 받던 재료는 setter로 변경해야한다.

      2b588ebe-2d36-4432-91cb-cfe5e9921ec8

      • Finished

        image-20220806172934736

      • Ready

        image-20220806173016138

      • Hit

        • 자신의 필드 cards를 사용하던 곳은 부모가 물려준 중복메서드인 템플릿 getter -> cards()를 내수용으로 사용한다.
        • 자신의 생성자 주입으로 상태를 받았던 것을 부모가 물려준 중복메서드의 템플릿 setter를 이용해 물려받는 -> 부모의 field를 채우고 -> 나는 getter cards()만 사용할 수 있게 한다

        image-20220806173057219

  3. 추상클래스로 중복필드 + 생성자까지 제거 하다보니 -> 생성자가 없으면 public생성자상태가 되어버렸다.

    1. 하지만, 패키지내 중간객체 클래스 or 유틸메서드 클래스의 생성자들은 default와 private으로 잠겨있어야한다. image-20220806174625925

    2. 패키지내 시작객체 Ready를 제외한 모든 구상 상태겍체들의 보이지 않는 기본생성자를 재정의해서 default로 변경해주자

      01daf272-6ec1-4677-aaa8-56914dc508bf

      image-20220806174957587

  4. 상태객체의 인터페이스 오퍼레이터는 최소 4개+ @가 필요하다

    1. trigger 메서드: draw()

    2. 중단 메서드: stay()

    3. 현재상태값 반환메서드: getter cards()

    4. is특정[중간추상층]인지 확인 메서드: boolean isXXXX()

      • 여기서는 stay개발시 불가하여 묶여진 Finished 형용사 중간추상층이 이미 있으니 끝난 상태냐라고 물어볼 수 있다.

        • 특정 구상체인지는 물어보지 않는다. 그놈빼고 다 false를 대답하기 때문에 -> 특정 구상체인지는 물어보지말고 메세지를 던진다.
      • 다형성 인터페이스에 존재하는 boolean 메서드는 -> 구상체들이 구현만 해주면 알아서 개별 답변되는 메서드

        image-20220806180149358

  5. State인터페이스에 특정상태(그룹, 중간추상층) 확인 메서드를 구현하고

    • Started는 건너띄고, Finished에서는 True / 나머지 구상체 Ready, Hit에서는 False를 return하자

      image-20220806182142251

      • Finished에서 구현했다면, 추클로서 중간에 중복을 먹어 구현하였으니 final을 달아야한다

      cb59d4e9-72ca-45c4-ac00-88d08c67c334

      image-20220806182052780

      image-20220806182039996

      image-20220806182119187

  6. 상태객체의 인터페이스 오퍼레이터는 최소 4개+ @가 필요하다

    1. trigger 메서드: draw()
    2. 중단 메서드: stay()
    3. 현재상태값 반환메서드: getter cards()
    4. is끝난상태[중간추상층]인지` 확인 메서드: boolean isXXXX()
    5. 현재 상태객체를 만드는 상태값Cards를 이용해서, 정보를 가진놈이 기능을 가진다
      • 정보전문가패턴에 따라, 상태값으로 가능한 연산은, 가진 놈이 한다.
      • cards를 가진 state객체가, cards로 계산하는 것을 한다.
  7. 상태별 수익계산 profit()메서드를 State인터페이스에 구현해보자.

    • 만약, 추상체변수만의 구상체별 자동 구현을 모른다면? state객체를 보유한 player 내부에서

      • state.isBlackjack()? -> 블랙잭 계산

      • state.isBust()? -> 0 반환

      • state.isStay()? -> Stay 계산

      • 하나하나 다 확인해서 그에 따른 계산을 해줘야한다.

        image-20220806183305823

    • 추상체변수의 구상체별 기능 구현전략패턴처럼 구현만 하면 알아서 적용된다.

      • 이미 외부생성(전략패턴) or trigger메서드(상태패턴)에 의해 State state안에는 Blackjack이나 Bust나 Stay가 담겨있는 상태다
        • 개별로 구현만 해주면, 알아서 작동한다.
      • 대신, 알아서 계산안되어야할 놈들은 thr나 예외처리 해놔야한다.
    • 추상체 변수의 상태이용 개별구현 메서드는 구상체를 물어보지말고 구현만 해놓으면 알아서 적용된다.

      • 상태값이 일반 객체였으면… 물어보고 판단했을 것인데… 추상체변수인 순간 알아서 구현됨을 생각하자.
  8. State 인터페이스에 전략패턴과 달리, 구상체들이 가진 상태값cards를 바탕으로 계산하는 기능인 profit을 구현하되, 파라미터로 [계산마다 바뀔 수 있는 필요정보 betMoney]를 인자로 받자

    • 구상체들이 상태값을 가지는 경우, 인터페이스 오퍼레이트는 상태값을 이용한 계산 기능도 추가할 수 있다.

      • getter도 그랬고..
    • 인터페이스에 메서드를 올리는 순간 알아서 구상체별로 개별구현되는 메서드구나 생각하자

      image-20220806185128475

    • 계산의 대상은 이미 끝난 상태의 Finished만 해당이 된다.

      • Ready와 Hit는 예외상황으로 먼저 막아놓자.

        7512c061-e33e-47a6-be67-89107122610d

        image-20220806185930878

  9. 구상체들에서 개별구현되니 일단은 중간추상층(Finished)는 건너띄고 구현한다

    • Blackjack -> betMoney의 2배 반환 image-20220806190556279
    • Bust -> -1을 곱해 반환 (그만큼 손해)
    • Stay -> 추가정보가 더 필요하니 계산말고 그냥 반환
  10. 중간추상층이 좋은 부모가 되려면, 공통점이 아예 없는 개별구현로직 조차, 개별구현훅메서드를 래핑하고 있는 public final 템플릿메소드로 가지고 있고, 서로 다른 로직은 protected abstract 훅메서드만 내보내줘야한다.

    1. CompareFile(ctrl+D)로 구상체 메서드들의 공통점과 차이점을 살펴보고, 어느부분까지를 훅메서드로 뺄 것인지 생각한다.

      • 상수만 서로 달라도, 해당부분을 훅 메서드로 뺄 수 있다.
    2. 구상체들 중 서로 다른 훅 부분에 가장 파라미터가 많이 사용되는 로직을 가진 구상체 1개를 선택하고 해당 메서드를 중간추상층으로 잘라내서 올린다

    3. 올린 public 템플릿메서드 내부에서 다른 부분만 내수용 메서드 추출(구상체private -> 추상체protected abstract)의 훅메서드로 만든다

      • 올리고 나서 훅을 만든다면, 메서드 추출후 가시성을 private -> protected abstract로 직접 변경해줘야한다.
      • 올린 템플릿메서드는 final로…
    4. 나머지 구상체들은, 훅메서드를 구현하면서, 기존 구현메서드의 다른 부분만 훅에 입력한 뒤, 기존 메서드는 삭제해준다.

      e46c06eb-6722-429a-8311-3ff3f0fb6af9

      image-20220806225502853

      image-20220806225516439

    5. public 템플릿메소드는 final을 달아준다. 깜빡했으면… image-20220806225611306

  11. 중복제거 등 추상화/상속 관련작업이 끝날 때마다, diagram을 보자

    • 복잡하다면, 생성자+method만 키고 보자.

    image-20220806230604477

    • 추상화 레벨을 보고 싶다면, 바로 위의 추상클래스에 데고 usage(shift+F12)를 통해 어떤 놈들이 나를 직접 extends했는지확인하면 된다.

      image-20220806231001237

  12. Finished 기존 중간추상층과 추상화레벨을 맞추기 위해 ReadyHit구상체의 공통점과 차이점을 확인한다.

    • draw, stay는 구현내용이 개별구현으로 100% 달라서 일단 유지하고

    • profit()과 isFinished()는 비슷한게 아니라 완전히 동일하므로, 추상층에 올릴 것이다.

      image-20220806232414858

  13. finished에 대항하는 isFinished false를 가진 형용사 중간추상층 Running을 만든다.

    1. Started를 구현하고, Ready에서 공통메서드만 중간에서 먹어준다.

    2. abstract클래스며, 중복코드를 제거하는 템플릿메소드들은 final을 달아준다.

      449c9322-56b1-419d-88c8-13a04d0463b5

      image-20220806232957330

      image-20220806233009526

      image-20220806233220821

  14. 이제 추상화레벨이 맞춰졌다.

    image-20220807000217047

  15. 부가적인 처리로서, 자식들은 훅메서드만 가지도록 좋은 부모 만들기의 일환으로

    • 100% 로직이 달라도 메서드명만 같다면, 100% 훅메서드인 포장 public final template으로 올리자.

    image-20220807001054996

    • 문제는 오퍼레이터 메서드명은 유지되어야하는데, 100% 훅메서드명을 동일하게 못잡으니 어렵다.

      • draw -> drawFrom

      • stay -> stayEach

        ef036983-3849-4d1c-a20a-f62a28f58644

      image-20220807001808286

      image-20220807001832475

      image-20220807001851504

    • 자식 구상체들은 훅만 소유

      image-20220807002018941

  16. Finished에서도 좋은 부모를 만들기 위해 템플릿메서드로 올리고, 자식들은 훅만 소유하도록 변경해보자.

    image-20220807002057853

    • draw를 중간추상층의 템플릿메서드로 올리자

      • 100%로직이 같아서 훅메서드를 뽑을 필요가 없었다.

        1388bb0d-489b-470a-98f9-ab96ad095260

      • final을 달아주는 것을 깜빡해서 달아주자.

        image-20220807002635494

      image-20220807002715139

  17. 그러고보니, 중간추상층 2개도 상위 추상클래스로 올릴 수 있을 것같은데, 훅을 메서드 vs 안가진 메서드 등의 차이도 있고, 카테고리 문제도 있어서 일단 여기까지만 처리한다.

    • 구상체(자식들)은 모두 훅메서드만 가졌다.
    • 전략패턴으로 변경된다면, 전부 전략메서드만 구현하게 되는 것이다.

    image-20220807003348526

  18. 이제 상태객체를 추상체변수로 소유해서 사용하는 Player를 다른 패키지에서 생성한다.

    • 상태객체는 trigger에 의해 계속 변화므로 not final 필드초기화+ set계열메서드로 가지고 있는다.

    • 상태를 바꾸는 set계열이지만, 이미 정해진 종류의 상태를 가진다면 set대신 public void toggle에 가깝다.

      • 필드초기화시 유일하게 열린 Ready 로 만든다.

      d8da10a5-85d0-4523-8dd7-7b5c05fc2bd9

      image-20220807010832575

  19. 메서드가 호출객체(자신)의 상태가 변하는 메서드(setter/toggle계열)때, 호출객체(state) 상테에 따른, 제한[호출시 예외발생코드가 존재]이 있는 action이라면, if문으로 [예외발생 코드 상태로 진입못하게 하는] 상태condition을 확인후 시행해야한다.

    • state가 blackjack, bust, stay로 업데이트되었다면, draw를 호출 시 예외가 발생한다.
    • 애초에 해당 상태(Finished)가 아닐때만 호출하도록 condition을 걸고 시행한다
      • 예외발생 코드를 짜놨찌만, 그쪽에 도달하지 못하게 막아주는 것이다.

    image-20220807011225148

  20. 만약, 매번 확인하며 자신을 업데이트하는 작업을 여러번 && 조건불만족시까지 반복하고 싶다면 -> if condition + set계열 action의 반복문이 while이다.

    • 이 때, 파라미터는 Cards의 컬렉션 뭉태기가 아니라 반복이 끝날때까지, iterator로서 1개씩 재료 제공메서드()getter를 제공해주는 객체가 파라미터로 와야한다.

      • 1번일 때 파라미터가 Card라고 해서 -> Cards 나 Cards컬렉션을 생각하지말고 iterator같은 객체를 생각하자. public 메서드의 파라미터 == 외부에서 생성되어 들어옴 -> 도메인 객체를 외부에 함부러 -> 허용하지말자

        • my) 데이터(도메인)객체를 함부로 외부에서 생성하도록 public 메서드의 파라미터로 받지말자. -> 내부에서 생성하거나, 내부에서 iter로 받자 -> 도메인객체의 메서드가 public으로서 외부에 공개된 것이면 상관없지만, 일단 내부생성/캡슐화 조달(iter)로 받자
      • 반복이 끝날때까지, 매번 재료를 1개씩 제공해줘야한다.

        92e5fda9-5ab5-4e79-84dd-43f21d423298

      image-20220807012819734

  21. 카드를 1장씩 제공해줄 수 있는 Deck을 만들어보자.

    1. Deque의 재료 컬렉션을 생성할 땐, ArrayList로 일단 생성하고

    2. 제공할 때, 얕은복사(생성자복사)시 new ArrayDeque<>( list )생성자 복사시 형 변환하여 deque를 제공해주면 된다.

    3. 이 때, 랜덤으로 섞인 것을 제공해주려면, ArrayList를 먼저 shuffle한 뒤, deque로 생성자복사해서 제공한다

      image-20220807123437674

    4. 가변 콜렉션 -> append을 써서 생성하는 것은 stream으로 묶어서 일괄처리가 가능하다.

      1. 2중 for문은 stream -> map -> stream -> 기존map을 flatmap후 바깥에서 collect하면 된다.

        51f7b591-8476-421e-ae82-602aff5abaaa

        image-20220807124141031

        image-20220807124150334

        image-20220807124206100

    5. 생성자에서 쓰이는 내부context(보라색)은 필드인데, 내부 필드 초기화메서드 추출시 안뽑히고 내수용메서드가 되어버리니, 필드 초기화하는 = 우항값을 지역변수로 뽑아서, [필드 = 초기화]가 아닌 [지역변수 할당 = ]해서 필드초기화는 제외시키고, 위쪽만 메서드 추출하자.

      • 메서드 추출시, 필드 초기화 부분은 제외시키자!

      • 메서드 추출시, 다른 곳에 위임되지 않을, 내부context필드 초기화 && 재할당은 할당값(우항)만 따로 지역변수로 빼놓고, 해당 context를 제외시키고 추출한다

        • 만약, 내부context를 다른쪽에 위임하는 로직이면, 내부context를 지역변수로 빼서 위쪽으로 놓고, 해당 지역변수가 파라미터로 잡히게 한다.

        e90db055-47b1-4b09-87e0-6cfefaeea16f

        image-20220807125203951

        image-20220807125500504

  22. 랜덤이 포함된 로직은, 보이자마자, 전략패턴적용을 위해, 메서드추출 -> 전략객체.메서드()호출 -> 전략객체에 위임 -> 전략 인터페이스 생성으로 빼놓자.

    1. 랜덤이 껴있는 전체로직( 객체 반환까지 )을 1개의 메서드로 추출한다.

      b92ff93b-fe78-4ced-98ce-19005afb7f4b image-20220807133924318

    2. 위임을 위해 예비전략메서드의 인자에 new 랜덤전략객체()를 넣어서 파라미터로 추가한다. 내부에선 사용하진 않는다.(위임하기 위해 만든 객체라서 원래 사용된 상태)

      • 파라미터에 전략객체가 있어야 -> 파라미터 속 1개의 객체로 위임할 수 있다.

        ba7258b3-b722-4898-844e-885391718b77 image-20220807134122277

    3. 내부 메서드가 있다면, 모두 new 전략랜덤객체를 파라미터로 추가해준다.

      • 내부 메서드들 내부의 this context도, 모두 전략객체로 교체

        dc294698-a194-497d-b171-1f2202bfb67b

        image-20220807134713089

        • this가 메서드레퍼런스에 껴있으면, 다시 람다식으로 풀어서 파라미터 추가

          793c853e-9619-4b27-8bb4-72be98de434d

          image-20220807134845380

      • 보라색 context가 껴있으면, 파라미터로 추출

    4. 내부메서드들부터 F6으로 위임한다

      1. 만약, 바깥부터 하면, 내부 내수용메서드 == Deck메서드 == Deck가 파라미터로 달린체로 위임된다

        image-20220807135027659

      2. 아주 안쪽 && 먼저 실행되는 내수용 메서드들부터 위임하자

        e0ddd88d-dfe2-4f6d-ae02-75f3774f3187

        image-20220807135410544

        image-20220807135604705

        • 위임된 내수용메서드들은 수동으로private으로 바꿔주자.(옮길때는 위임해준 쪽으로 넘겨줘야해서 public이 자동으로 걸린다.)

          image-20220815143724053

    5. 이제, 특정 전략객체가 아닌, 전략인터페이스를 파라미터를 통해 외부에서 주입받아서 소유하도록 하기 위해, 구상체로부터 전략인터페이스를 만들어 올린다.

      1. 구상클래스에서 @Override -> extract interface -> 전략인터페이스 생성 -> 전략메서드만 추출

      2. 구상체 사용처(Deck)에서 파라미터 추출 -> 생성자로는 전략인터페이스가 들어오도록 수정

        04fcb343-b1f7-4482-923a-b29a430261d8

        image-20220807140331139

  23. 이제 Deck에서는 랜덤Deque카드들을 소유한 상태이므로 Player에게 pop으로 1개씩 제공해준다.

    • Player는 while문으로 상태확인하면서, Deck으로부터 1개씩 재료를 iterator처럼 제공받는다.

      2a024204-2c01-4410-8846-43e87e0130b9

      image-20220807140606654

  24. 하나의 기능만 뺀 전략패턴은 람다식을 편하게 사용하도록 애너테이션을 붙여준다.

    image-20220807140849701

  25. RandomCardGenerator는 List로 종류별로 만든 카드 List -> Shuffle후 -> Deque로 반환해주는데, Deque의 재료 List는 1번만 생성해놓고, 필요시 shuffle후 생성자복사만 매번 해주면 된다.

    • 즉, 카드종류별 List는 static 필드에 미리 1번만 생성해놓으면 된다.

    95fd73d8-8e5c-488f-8f2a-990018331ded

    image-20220807223426856

  26. 이제 Random로직이 보여서 묶어서 만든 전략패턴의 수동전략 생성 or 람다식 대입을 만들어보자.

    • 수동전략은 사실상 Test에서 밖에 안쓰이므로 도메인에 전략객체 생성을 안한다
    • 람다식으로 바로 대입이 불가능하면, 수동전략 클래스를 testutil에 추가해준다.
  27. 전략객체가 주입되는 객체를 생성하고, 전략객체 주입부가상인자 람다식으로 [전략메서드 대신구현]이 가능한지 보자.

    • 확인해보니 , Deck의 생성자에서 쓰고 있다 -> 테스트에서 new Deck() 을 만들자.

    image-20220807232206299

    image-20220807232220504

    image-20220807232136058

    • 람다식으로 전략메서드 로직을 구현하여 시그니쳐에 맞는 응답을 해줄 수 있는지 확인하자.

      • 전략메서드는 Deque<Card>new ArrayDeque<Card>();를 채워서 응답해주면 된다.

      image-20220807232417754

    • 람다식 내에서 수동으로 채워주기엔 무리가 있으니 위쪽에서 지역변수로 채워놓고, 해당 지역변수를 넣어줘도 된다.

      image-20220807232534896

    • 카드2, 5를 수동으로 채워서 deck을 pop해보면, 뒤쪽 5가 나온다

      f832a82d-af31-4fe9-b9b4-6f6eec5de5e7

      image-20220807234741623

  28. 수동으로 전략메서드 시그니쳐 응답값 구현을 반복해서 사용할 예정이라면 메서드 추출후, Fixed수동전략객체Class에 위임하여 재활용한다.

    • 이 때, 수동으로 넣어주는 값들을 변수로 빼서 사용할 수 있다.

      • 랜덤카드생성은 정해진 수의 카드만 가능
    • 수동카드생성은, 파라미터로 원하는 요소들로 짧게 구성 가능

      0e9ce492-833c-4223-9967-e9ba993ed2d2

      image-20220807235828447

    • 원하는 수만큼 받을 수 있게 2번째 파라미터부터를 가변배열로 받아서 처리한다

      ff728444-5533-42f4-b85a-46dc1df35067

      image-20220808000033000

  29. 이 때, 테스트용 수동 전략이 적용된 객체생성까지를 하나의 fixture생성으로 보고 묶을 수 있다.

    5c74f3aa-ecd0-44be-8def-b2e385d5a2cd

    image-20220808001610654

  30. 캐싱객체 or 불변Vo객체의 Fixture 모음이 아니라 1개의 FixtureGenerator로서 testutil패키지지 > FixtureGenerator클래스를 만들고, 생성 유틸메서드로 추출하여 재활용할 수 있다.

    1. testutil패키지 아래 FixtureGenerator 클래스를 만든다

    2. private메서드를 유틸메서드(Fixture정팩매)로서 public static을 붙인 뒤 위임한다

      4a1139ce-e8ef-4c9e-8517-65e790c15489

      image-20220808002002988

      image-20220808002015770

  31. player는 카드1장 받으면 Ready이 되어야한다.

    f4b91d9f-4f26-40f7-a902-7abf144f62df

    image-20220807224704847

    • 하지만, 최초의 Ready상태에서 Started에 선언된 공통필드가 null상태라서 getter호출시 에러가 난다.

      image-20220807224602497

      image-20220807224613893

      image-20220807224629139

      image-20220807224536611

      image-20220807224642213

  32. 교훈) not final + setter계열 조합 for좋은부모는 null참조가 될 수 있으니 반드시 초기값 필드초기화를 해주자

    image-20220807224811919

    image-20220807224832856

  33. 2장이상 draw하면, Ready가 아닌 상태가 된다.

    image-20220807225214117

  34. Player가 Deck을 전달받으면 finished가 될때가지 draw로 뽑아간다.

    • 이럴 경우, hit상태도 !finished라서 계속 반복하여 stay를 못한다

    image-20220807225449880

    • deck을 받아도 finished까지 반복이 아닌 1장만 받도록 수정해보자.

    image-20220807230956468

    image-20220807231031376

    • 만약, ! 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개만 주면 되긴 하는데..