📜 제목으로 보기

전략 패턴 적용하기

[랜덤 최소단위 메서드]를 전략메서드로 바꿔 [수동]도 가능하게 하기

전략부위가 만약, 메서드화 안되어있으면(ex>조건식) 메서드화 (추출 등)
  • 메서드명을 랜덤+수동다 포함하는 일반화 + 목적어 뺌(전략인페+구현전략들에 명시됨)된 전략메서드로 바꿔주기
랜덤 최소단위 메서드 정의부 -> 호출()부 찾기
  1. 랜덤 메서드 정의부 찾기 image-20220312131559611

  2. 랜덤 메서드의 호출부 찾기 image-20220312131647458

##### 랜덤 메서드 호출부에서 랜덤 메서드명을 -> 수동포함하는 일반화된 전략메서드명() + 목적어 제거으로 변경 -> 전략인페를 상속한 new 랜덤전략구현체 객체(). 전략메서드() 인척 빨간줄 생성해주기

  1. 일반화된 메서드명 상태에서 -> 빨간줄로 외부주입된 랜덤전략구현체 객체 + 전략메서드()호출해준다.

    • 기존상태( 메서드명을 랜덤+수동다 포함하는 일반화된 + 목적어빠진 전략메서드명으로 바꿔주기)

      image-20220312134713439

      • 전략메서드에 목적어 넣지마라

      image-20220312135004891

        public static List<Lotto> issueLottoByCount(LottoCount lottoCount) {
            final List<Lotto> lottos = new ArrayList<>();
            while (lottoCount.isNotFinish()) {
                lottoCount = lottoCount.decrease();
                lottos.add(issue());
            }
              
            return lottos;
        }
              
        public static Lotto issue() {
            Collections.shuffle(LOTTO_NUMBERS);
            return new Lotto(LOTTO_NUMBERS.stream()
                             .limit(6)
                             .sorted()
                             .collect(Collectors.toList()));
        }
      
  2. 외부주입된 new 랜덤전략객체에서 호출한 전략메서드()인척 해서 빨간줄로 생성하기

    image-20220312135034060

     public static List<Lotto> issueLottoByCount(LottoCount lottoCount) {
         final List<Lotto> lottos = new ArrayList<>();
         while (lottoCount.isNotFinish()) {
             lottoCount = lottoCount.decrease();
             lottos.add(new RandomLottoIssuer().issue());
         }
        
         return lottos;
     }
    

    image-20220312135300826 image-20220312135321651

  3. 전략메서드도 작성 image-20220312135417506

랜덤전략객체.전략메서드()로 <- [ 기존 랜덤 메서드 ]에 있던 랜덤 로직 옮겨주기
public class RandomLottoIssuer {
    static final List<LottoNumber> LOTTO_NUMBERS;

    static {
        LOTTO_NUMBERS = IntStream.rangeClosed(LottoNumber.MIN, LottoNumber.MAX)
            .mapToObj(LottoNumber::getInstance)
            .collect(Collectors.toList());
    }

    public static Lotto issue() {
        Collections.shuffle(LOTTO_NUMBERS);
        return new Lotto(LOTTO_NUMBERS.stream()
                         .limit(6)
                         .sorted()
                         .collect(Collectors.toList()));
    }
}
외부주입 전략객체를 -> 외부파라미터로 받기 위해서 -> 실제 전략인페 정의해주기
  1. 앞에서 정의한 랜덤전략객체를 보고 전략인페를 생성한다.

    • RandomLottoIssuer -> LottoIssuer -> 앞으로 xxxxLottoIssuer 등

    • 전략인페명으로 패키지를 만들어서 다같이 넣어주면 좋다. image-20220312140142253

      image-20220312140155431 image-20220312140216397

  2. 전략메서드명은 다 통일이다. 목적어없이 동사만

    • issue()

    • 특정전략메서드의 반환타입을 참고해서 추상메서드를 정의해준다.

      • 랜덤전략의 메서드 반환타입 보고 image-20220312140307738

      • 정의부 복붙해와서 반환타입 + 메서드명빼고 다 달리기 image-20220312140410033

        image-20220312140421064

        image-20220312140546308

          public interface LottoIssuer {
              Lotto issue();
          }
        

new 외부전략객체() 생성하던 곳으로 돌아가서 -> 변수/파라미터는 추상체로 외부주입 받아, 추상체.전략메서드()하도록 한다.
  1. 기존: 외부주입없이 + 구상체로 사용

    image-20220312140728576

     public class LottoFactory {
        
         public static List<Lotto> issueLottoByCount(LottoCount lottoCount) {
             final List<Lotto> lottos = new ArrayList<>();
             while (lottoCount.isNotFinish()) {
                 lottoCount = lottoCount.decrease();
                 lottos.add(new RandomLottoIssuer().issue());
             }
        
             return lottos;
         }
     }
        
    
호출부 중 전략객체만 선택해서 파라미터로 추출
  1. 전략메서드를 제외한 전략객체만 선택해서 추출하면 된다. image-20220312140839186 image-20220312140847831

  2. 추상체 이름과, 파라미터 Type모두 추상체로 바꿔준다. image-20220312140923314

    image-20220312140931250

     public class LottoFactory {
        
         public static List<Lotto> issueLottoByCount(LottoCount lottoCount, final LottoIssuer lottoIssuer) {
             final List<Lotto> lottos = new ArrayList<>();
             while (lottoCount.isNotFinish()) {
                 lottoCount = lottoCount.decrease();
                 lottos.add(lottoIssuer.issue());
             }
                
             return lottos;
         }
     }
    
처음에는 내부에서 특정 전략객체를 만들어썻찌만 -> 파라미터 추출하면 알아서 외부에서 주입된다
  • 그 장소가 service라도 괜찮다. spring에서는 알아서 service에서 외부주입 받는다.

    image-20220312141339520

      public class LottoService {
        
          public static List<Lotto> issueByMoney(final Money money) {
              final LottoCount count = money.getCount();
        
              return LottoFactory.issueLottoByCount(count, new RandomLottoIssuer());
          }
    
이제 전략인페를 impl 수동 구현만 -> TDD에서 수동메서드 완성해보자.
  1. 수동 전략impl 구현체를 생성한다. image-20220312141544414

    image-20220312141638620

  2. 관련 테스트(LottoFactoryTest)로 가서, 해당메서드를 완성해야한다.

    • test에서 외부전략객체생성전략메서드()호출해도 아무도일도 안일어날 것이다. image-20220312141926117

      image-20220312141931627

  3. 미리 작성된 RandomLottoIssuer를 띄워놓고(tabmover -> ctrl+alt+shift+D)로 참고하면서 내용을 채워나가면 된다.

    image-20220312142342869

대박) 전략메서드 준비물을 [메서드() 인자]가 아닌 -> 각 전략별 [전략객체 생성자/정펙매]에서 받아 준비해두자
  • 랜덤로직을 랜덤전략객체에 옮기고 나니 랜덤에 필요한 것들이 생성자나 변수에서 처리되고 있었다.

    • 각 전략별 전략메서드 실행시 필요한 준비물은 각 전략객체 생성자/정펙매에서 파라미터로 받자

    image-20220312142334323

      public class RandomLottoIssuer implements LottoIssuer {
          static final List<LottoNumber> LOTTO_NUMBERS;
        
          static {
              LOTTO_NUMBERS = IntStream.rangeClosed(LottoNumber.MIN, LottoNumber.MAX)
                  .mapToObj(LottoNumber::getInstance)
                  .collect(Collectors.toList());
          }
        
          public Lotto issue() {
              Collections.shuffle(LOTTO_NUMBERS);
              return new Lotto(LOTTO_NUMBERS.stream()
                  .limit(6)
                  .sorted()
                  .collect(Collectors.toList()));
          }
      }
    
  1. 수동로또준비물은 수동 입력된 번호 List일 것이다.

    image-20220312142852039

     @Test
     void manual_issue() {
         // 전략들의 준비물은 전략메서드()에서는 따로따로 못받는다. -> 전략객체 만들때 미리 준비되어있어야한다.
        
         //1. 수동로또전략의 준비물은 [1,2,3, 수동입력]이 있어야 -> issue()로 [1,2,3, 수동로또]를 발급할 수 있을 것이다.
         // -> input은 나중에 처리하고, 정제된rawInput을 생성자에서 받아주자.
        
         //final Lotto lotto = new ManualLottoIssuer().issue();
         final Lotto lotto = new ManualLottoIssuer(1, 2, 3, 4, 5, 6).issue();
     }
    
    • 일단 가변인자로 받도록 만들어놨으니… 저렇게 처리했음.
  2. 전략객체 생성시부터 준비물 -> 변수에 받아준비해두자

     public class ManualLottoIssuer implements LottoIssuer {
        
         private final Integer[] value;
        
         public ManualLottoIssuer(final Integer... value) {
             this.value = value;
         }
        
         @Override
         public Lotto issue() {
             throw new UnsupportedOperationException("ManualLottoIssuer#issue not implemented.");
         }
     }
    
  3. 전략메서드에서 준비물을 사용해서 처리해주자.

     public class ManualLottoIssuer implements LottoIssuer {
        
         private final Integer[] value;
        
         public ManualLottoIssuer(final Integer... value) {
             this.value = value;
         }
        
         @Override
         public Lotto issue() {
             //throw new UnsupportedOperationException("ManualLottoIssuer#issue not implemented.");
             //2. 전략객체가 생성시 받아둔 준비물을 가지고 전략메서드로 반환하자.
             return new Lotto(value);
         }
     }
        
    
  4. 가장 기본적인 일급테스트인 size + contains만 테스트 해줬다.

     @Test
     void manual_issue() {
         final Lotto manualLotto = new ManualLottoIssuer(1, 2, 3, 4, 5, 6).issue();
        
         assertThat(manualLotto.getValue().size()).isEqualTo(6);
     }
    
     @Test
     void contains() {
         final Lotto manualLotto = new ManualLottoIssuer(1, 2, 3, 4, 5, 6).issue();
        
         assertThat(manualLotto.getValue()).containsExactly(
             new LottoNumber(1),
             new LottoNumber(2),
             new LottoNumber(3),
             new LottoNumber(4),
             new LottoNumber(5),
             new LottoNumber(6)
         );
     }
    
초대박) 테스트용 람다 전략은 전략객체 대신이 아니라 내부 전략메서호출()될 응답까지를 미리 주는 것 -> 테스트에서 내부랜덤결과(전략메서드 응답type)을 외부 객체주입시 넣어줄 수 있다
  1. 전략객체를 주입받는 메서드(전략메서드 호출()부를 감싸는 메서드)에 new 전략객체를 입력해서

    • 내부에서 서로다른 전략메서드() 호출 -> 전략마다 다른 응답값을 사용하는 것이 기본 패턴인데

    image-20220312151533837

    image-20220312151549205

  2. 람다전략은 new전략객체 대신 ` () -> 전략메소드의 응답(Lotto)`까지 한번에 주입한다.

    1. 예시1: 전략객체가 들어가던 boolean응답 전략메소드의 전략객체 주입

       js.move(new RandomMoveConditionStrategy());
              
       js.printPosition();
      
       public void move() {
           if(isMovable()){
               this.position++;
           }
       }
      
      • 전략객체 대신 람다 () -> boolean응답까지 미리 지정해서 전략자리에 대입
       jk.move( () -> true);
              
       jk.printPosition();
      
  3. 로또에 적용해보면, 2번째 인자로 전략객체를 받아 -> 그에 맞는 전략메서드()내부 호출하는 issueLottoByCount( , )

     public static List<Lotto> issueLottoByCount(LottoCount lottoCount,
                                                 final LottoIssuer lottoIssuer) {
         final List<Lotto> lottos = new ArrayList<>();
         while (lottoCount.isNotFinish()) {
             lottoCount = lottoCount.decrease();
             lottos.add(lottoIssuer.issue());
         }
        
         return lottos;
     }
    
  4. 여기에 2번째 인자에 테스트용 람다전략으로서 전략메서드 응답값을 포함한 람다전략을 대입하자

     @Test
     void lambda() {
         final List<Lotto> lottos = LottoFactory.issueLottoByCount(
             new LottoCount(1),
             () -> new Lotto(1, 2, 3, 4, 5, 7));
         // 외부 전략객체가 들어와야하는데, 람다(내부전략메소드 호출후 응답갑까지 미리)를 대입
        
         assertDoesNotThrow(() -> lottos.get(0).contains(new LottoNumber(7)));
     }
    
요약: [전략객체 ]자리에 내부.전략메서드()호출 끝난 응답값까지 구현하여 전달하는 람다전략