전략패턴 2(자동로또->수동)
자동차경주에 이어 2번째 전략패턴
📜 제목으로 보기
- 전략 패턴 적용하기
- [랜덤 최소단위 메서드]를 전략메서드로 바꿔 [수동]도 가능하게 하기
- 전략부위가 만약, 메서드화 안되어있으면(ex>조건식) 메서드화 (추출 등)
- 랜덤 최소단위 메서드 정의부 -> 호출()부 찾기
- 랜덤전략객체.전략메서드()로 <- [ 기존 랜덤 메서드 ]에 있던 랜덤 로직 옮겨주기
- 외부주입 전략객체를 -> 외부파라미터로 받기 위해서 -> 실제 전략인페 정의해주기
- new 외부전략객체() 생성하던 곳으로 돌아가서 -> 변수/파라미터는 추상체로 외부주입 받아, 추상체.전략메서드()하도록 한다.
- 호출부 중 전략객체만 선택해서 파라미터로 추출
- 처음에는 내부에서 특정 전략객체를 만들어썻찌만 -> 파라미터 추출하면 알아서 외부에서 주입된다
- 이제 전략인페를 impl 수동 구현만 -> TDD에서 수동메서드 완성해보자.
- 대박) 전략메서드 준비물을 [메서드() 인자]가 아닌 -> 각 전략별 [전략객체 생성자/정펙매]에서 받아 준비해두자
- 초대박) 테스트용 람다 전략은 전략객체 대신이 아니라 내부 전략메서호출()될 응답까지를 미리 주는 것 -> 테스트에서 내부랜덤결과(전략메서드 응답type)을 외부 객체주입시 넣어줄 수 있다
- 요약: [전략객체 ]자리에 내부.전략메서드()호출 끝난 응답값까지 구현하여 전달하는 람다전략
- [랜덤 최소단위 메서드]를 전략메서드로 바꿔 [수동]도 가능하게 하기
전략 패턴 적용하기
[랜덤 최소단위 메서드]를 전략메서드로 바꿔 [수동]도 가능하게 하기
전략부위가 만약, 메서드화 안되어있으면(ex>조건식) 메서드화 (추출 등)
- 메서드명을
랜덤+수동다 포함하는 일반화 + 목적어 뺌(전략인페+구현전략들에 명시됨)
된 전략메서드로 바꿔주기
랜덤 최소단위 메서드
정의부 -> 호출()부
찾기
-
랜덤 메서드 정의부 찾기
-
랜덤 메서드의 호출부 찾기
##### 랜덤 메서드 호출부에서 랜덤 메서드명을 -> 수동포함하는 일반화된 전략메서드명() + 목적어 제거
으로 변경 -> 전략인페를 상속한 new 랜덤전략구현체 객체(). 전략메서드()
인척 빨간줄 생성해주기
-
일반화된 메서드명
상태에서 -> 빨간줄로외부주입된 랜덤전략구현체 객체
+전략메서드()
호출해준다.-
기존상태( 메서드명을 랜덤+수동다 포함하는
일반화된 + 목적어빠진 전략메서드명
으로 바꿔주기)- 전략메서드에 목적어 넣지마라
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())); }
-
-
외부주입된 new 랜덤전략객체
에서 호출한전략메서드()
인척 해서 빨간줄로 생성하기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; }
-
전략메서드도 작성
랜덤전략객체.전략메서드()로 <- [ 기존 랜덤 메서드 ]에 있던 랜덤 로직 옮겨주기
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()));
}
}
외부주입 전략객체를 -> 외부파라미터로 받기 위해서 -> 실제 전략인페 정의해주기
-
앞에서 정의한 랜덤전략객체를 보고 전략인페를 생성한다.
-
RandomLottoIssuer
->LottoIssuer
-> 앞으로 xxxxLottoIssuer 등 -
전략인페명
으로 패키지를 만들어서 다같이 넣어주면 좋다.
-
-
전략메서드명은 다 통일이다. 목적어없이 동사만
-
issue()
-
특정전략메서드의
반환타입을 참고
해서추상메서드를 정의
해준다.-
랜덤전략의 메서드 반환타입 보고
-
정의부 복붙해와서
반환타입 + 메서드명
빼고 다 달리기public interface LottoIssuer { Lotto issue(); }
-
-
변수/파라미터는 추상체로 외부주입 받아, 추상체.전략메서드()
하도록 한다.
new 외부전략객체() 생성하던 곳으로 돌아가서 -> -
기존: 외부주입없이 + 구상체로 사용
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; } }
호출부 중 전략객체만 선택해서 파라미터로 추출
-
전략메서드를 제외한 전략객체만 선택해서 추출하면 된다.
-
추상체 이름과, 파라미터 Type모두 추상체로 바꿔준다.
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에서 외부주입 받는다.
public class LottoService { public static List<Lotto> issueByMoney(final Money money) { final LottoCount count = money.getCount(); return LottoFactory.issueLottoByCount(count, new RandomLottoIssuer()); }
이제 전략인페를 impl 수동 구현만 -> TDD에서 수동메서드 완성해보자.
-
수동 전략impl 구현체를 생성한다.
-
관련 테스트(LottoFactoryTest)로 가서, 해당메서드를 완성해야한다.
-
test에서
외부전략객체생성
후전략메서드()
호출해도 아무도일도 안일어날 것이다.
-
-
미리 작성된
RandomLottoIssuer
를 띄워놓고(tabmover -> ctrl+alt+shift+D)로 참고하면서 내용을 채워나가면 된다.
각 전략별 [전략객체 생성자/정펙매]에서 받아 준비해두자
대박) 전략메서드 준비물을 [메서드() 인자]가 아닌 -> -
랜덤로직을 랜덤전략객체에 옮기고 나니 랜덤에 필요한 것들이 생성자나 변수에서 처리되고 있었다.
- 각 전략별 전략메서드 실행시 필요한 준비물은
각 전략객체 생성자/정펙매에서 파라미터로 받자
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())); } }
- 각 전략별 전략메서드 실행시 필요한 준비물은
-
수동로또준비물은
수동 입력된 번호 List
일 것이다.@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(); }
- 일단 가변인자로 받도록 만들어놨으니… 저렇게 처리했음.
-
전략객체
생성시부터 준비물 -> 변수
에 받아준비해두자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."); } }
-
전략메서드에서 준비물을 사용해서 처리해주자.
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); } }
-
가장 기본적인 일급테스트인 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)을 외부 객체주입시 넣어줄 수 있다
초대박) -
전략객체를 주입받는 메서드(전략메서드 호출()부를 감싸는 메서드)
에 new 전략객체를 입력해서- 내부에서
서로다른 전략메서드()
호출 ->전략마다 다른 응답값
을 사용하는 것이 기본 패턴인데
- 내부에서
-
람다전략은
new전략객체
대신 ` () -> 전략메소드의 응답(Lotto)`까지 한번에 주입한다.-
예시1: 전략객체가 들어가던
boolean응답 전략메소드
의 전략객체 주입js.move(new RandomMoveConditionStrategy()); js.printPosition();
public void move() { if(isMovable()){ this.position++; } }
- 전략객체 대신
람다 () -> boolean응답
까지 미리 지정해서 전략자리에 대입
jk.move( () -> true); jk.printPosition();
- 전략객체 대신
-
-
로또에 적용해보면, 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; }
-
여기에 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))); }