Console 입력-검증-생성자 정리
getInput -> valid -> 객체 생성자
📜 제목으로 보기
- 콘솔 프로그램 입력/검증/생성자
콘솔 프로그램 입력/검증/생성자
-
자동차경주 1,2차 피드백(1단계 미션)후
콘솔입력 -> 검증 -> 개별 모델 생성자
구조를 생각해보았다. -
아직 Dto - domain 의존은 해결되지 않은 상황
- 나중에 참고할 것: DTO를 의존 하는 domain 문제 PR
InputView
재활용 SCANNER
-
스캐너를 사용하는 public static
getInput()
private static final Scanner SCANNER = new Scanner(System.in); public static String getInput() { return SCANNER.nextLine(); }
4단계 InputView.getXXX()
-
개별 모델input들을 의미하는
getXXX()
는 4단계로 구성됨.public static List<String> getCarNames() { OutputView.printRequestInstruction(REQUEST_CARS_NAME); List<String> carsName = Arrays.stream(getInput().split(SPLIT_DELIMITER, -1)) .map(String::trim) .collect(Collectors.toList()); Validator.validateCarNames(carsName); return carsName; } public static String getRound() { OutputView.printRequestInstruction(REQUEST_ROUND_VALUE); String inputRound = InputView.getInput(); Validator.validateInputNumber(inputRound); return inputRound; }
-
OuputView.print( 안내메세지 )
-
스캐너를 사용해서 받은, 복수형이면 split(,-1) by
Arrays.stream( ).map().collect()
/ 단수형이면 그냥List<String>
orString
-
복수형이면 실제이름XXXX를 붙이고 / 단수형이면 Input(String) or InputNumber(StringNumber)를 붙인
-
Input루트의 공통검증:
Validator.validate~
-> 재료는List<String>
아니면String(Number)
로 무조건 String타입을 검증대상으로 보게 정했다.-
복수형이면 check복수형(개별Empty, 중복) -> for validateInput or validateInputNumber의 단수 공통검증
private Validator() { // 유틸클래스는 따로 객체생성안한다는 private생성자로 표시 } public static void validateCarNames(List<String> carNames) { checkCarNamesEmpty(carNames); checkDuplicated(carNames); for (String carName : carNames) { validateInput(carName); } }
-
단수형이면 String이냐 StringNumber냐에 따라서 다르게
- String:
NullOrEmpty
+IncludeBlank
- StringNumber:
NullOrEmpty
+Format
(넣으면 IncludeBlank안넣어도 된다)
private static void validateInput(String carName) { checkNullOrEmpty(carName); checkIncludeBlank(carName); } public static void validateInputNumber(String inputRound) { checkNullOrEmpty(inputRound); checkValidFormat(inputRound); }
- String:
-
-
-
return
List<String> or String
(2번)
-
Controller
try/catch
-
입력받을 때,
입력 예외발생시 다시 입력받기
받으려면 **getXXXX()
가 **검증을 포함하는 부분
시켜서-
재귀호출하는 하도록 메서드화
되어 있고 그부분을try/catch
로 쌓여있여있어야하는데,
-
getXXX()
+ 받아서모델의 (정펙매)생성자 호출하는 과정
까지 try/catch로 감싸면,도메인 자체검증
도 try/catch의 재귀에 걸려도메인 자체검증 예외발생시에도 재입력
받을 수 있게 된다.- 객체 생성로직( 자체 검증 -> 객체 생성)까지 포함시켰으니, 해당 메서드는 try성공시 객체를 returnType으로
private Cars getCars() { try { return Cars.fromInput(InputView.getCarNames()); } catch (IllegalArgumentException e) { OutputView.printErrorMessage(e); return getCars(); } } private Round getRound() { try { return Round.fromInput(Integer.parseInt(InputView.getRound())); } catch (IllegalArgumentException e) { OutputView.printErrorMessage(e); return getRound(); } }
RacingGame racingGame = RacingGame.createRacingGame( getCars(), getRound(), new RandomNumberGeneratePolicy() );
Controller getXXX(): InputView.getXXX() 응답 String -> convert+생성자객체생성+try/catch
-
대신, 중간에 필요한 객체의 생성자까진 노출할 필요는 없다.
public class RacingGameController { public void startRacing() { RacingGame racingGame = RacingGame.createRacingGame( getCars(), // InputView의 getXXX()는 무조건 String -> 중간 객체(Name)도 다 노출... private Cars getCars() { try { return Cars.fromNames(Names.fromInput(InputView.getCarNames()));
- 지금은
Cars
를 만들기 위해Names
의 존재를 알아야 하는 상황인데요, 사실Names
는Cars
안에서만 사용되고 있죠. 그렇다면Names
의 존재가 바깥에 노출될 필요가 있을까요? 구현을 드러내고 있는 건 아닐까요?- 원시값 포장한 것은 굳이 생성자를 안드러내도 된다.
private Cars getCars() { try { return Cars.fromInput(InputView.getCarNames()); public static Cars fromInput(List<String> InputNames) { Names names = Names.fromInput(InputNames); return new Cars(names.getNames(
- 지금은
생성자
01 private 기본생성자
List<단일>
) ex> Cars
복수( private Cars(List<Car> cars) {
this.cars = cars;
}
단일객체 (포장객체들, Name+Position ) ex> Car
private Car(Name name) {
this.name = name;
this.position = Position.from(DEFAULT_POSITION_VALUE);
}
List<원시>
) ex> Names
일급컬렉션 (private Names(List<Name> names) {
this.names = names;
}
StringNumber (int ) ex> Round
private Round(int roundNum) {
this.roundNum = roundNum;
}
String (String ) ex> Name
private Name(String name) {
this.name = name;
}
List<CarDto>
) ex> CarsDto
복수Dto (private CarsDto(final List<CarDto> carsDto) {
this.carsDto = carsDto;
}
단일Dto (원시값들) ex> CarDto
public CarDto(String name, int position) {
this.name = name;
this.position = position;
}
String
or List<String>
)
02 .fromInput ( only - inputView에서 기본검증을 거치고 난 뒤, 자체 검증만 필요한 친구들
- 만약, stream.map 단일객체생성자 등장시 -> 단일객체로 검증이 옮겨짐
List<단일>
) ex> Cars
복수( public static Cars fromInput(List<String> InputNames) {
Names names = Names.fromInput(InputNames);
return new Cars(names.getNames().stream()
.map(Car::fromName)
.collect(Collectors.toList()));
}
단일객체 (포장객체들, Name+Position ) ex> Car
//
List<원시>
) ex> Names
일급컬렉션 (- 자체검증하는 대신, stream상에서 단일객체에서 검증이 옮겨짐.
public static Names fromInput(List<String> names) {
return new Names(names.stream()
.map(Name::from)
.collect(Collectors.toList()));
}
StringNumber (int ) ex> Round
public static Round fromInput(int inputRound) {
validateRound(inputRound);
return new Round(inputRound);
}
String (String ) ex> Name
//
복수Dto (List<CarDto>
) ex> CarsDto
List<CarDto>
) ex> CarsDto//
단일Dto (원시값들) ex> CarDto
//
03 .from( 원본재료 )
복수
- input없이 프로덕션내부 or 테스트에서 사용된다. -> 유틸화된 공통검증 + 자체 검증
- 만일, 원본재료가 포장된 객체라면, 자체검증은 포장객에게 갈 수 있다.
public static Cars from(List<Car> cars) {
return new Cars(cars);
}
VO 는 .from(원본 1개만)
- 정팩메 1개 -> input이지만 .from + 유일 자체 검증 생성자
public static Position from(int position) {
validatePosition(position);
return new Position(position);
}
List<단일>
) ex> Cars
복수( - test코드에서 Car -> Cars로 바로 생성하기 위해
public static Cars from(List<Car> cars) {
return new Cars(cars);
}
단일객체 (포장객체들, Name+Position ) ex> Car
- 일급컬렉션, 복수 쪽에서는
stream
을 통해원본 List
->단일객체 List
->일급
으로순으로 가기도 한다.
public static Car fromName(Name name) {
return new Car(name);
}
List<원시>
) ex> Names
일급컬렉션 (- test코드용
public static Names from(List<Name> names) {
validateNames(names);
return new Names(names);
}
StringNumber (int ) ex> Round
- test를 안하고, controller에서 마지막에 convert시킬거라을 input원본 받을일이 없음.
//
String (String ) ex> Name
- 복수, 일급컬렉션의 Names에서 stream으로 만들어지는 과정
- 테스트용
public static Name from(String name) {
checkValidLengthOfName(name);
return new Name(name);
}
List<CarDto>
) ex> CarsDto
복수Dto (-
일급컬렉션 내부에서 toDto()호출시 **
List<단일>
**유일 필드->List<단일Dto>
변환후 기본생성자로복수Dto
로 만들어줌 ->from일급컬렉
- 공통적인 기본 private
List<단일Dto>
를 호출해줄 public ->.from
public static CarsDto fromCars(final List<Car> cars) {
//todo: 5. cars -> carsdto의 정펙메( List<Car> ) -> List<CarDto> -> 일급 생성자
// carsDto의 생성자는 car list(cars)를 받아서 -> cardto list -> 일급 dto를 만드는 곳
final List<CarDto> carDtos = cars.stream()
.map(car -> CarDto.from(car))
.collect(Collectors.toList());
return new CarsDto(carDtos);
}
public static CarsDto from(final List<CarDto> carsDto) {
return new CarsDto(carsDto);
}
단일Dto (원시값들) ex> CarDto
복수or일급컬렉션
에서 stream에서Car -> CarDto
시 사용될.from원본( 1:1원본)
OuputView 테스트
에서원시값 필드들로 생성
하는단일Dto
-> 복수Dto
public CarDto(String name, int position) {
this.name = name;
this.position = position;
}