📜 제목으로 보기

콘솔 프로그램 입력/검증/생성자

  • 자동차경주 1,2차 피드백(1단계 미션)후 콘솔입력 -> 검증 -> 개별 모델 생성자 구조를 생각해보았다.

  • 아직 Dto - domain 의존은 해결되지 않은 상황

InputView

재활용 SCANNER

  1. 스캐너를 사용하는 public static getInput()

     private static final Scanner SCANNER = new Scanner(System.in);
        
     public static String getInput() {
         return SCANNER.nextLine();
     }
    

4단계 InputView.getXXX()

  1. 개별 모델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;
     }
    
    1. OuputView.print( 안내메세지 )

    2. 스캐너를 사용해서 받은, 복수형이면 split(,-1) by Arrays.stream( ).map().collect() / 단수형이면 그냥

      • List<String> or String
    3. 복수형이면 실제이름XXXX를 붙이고 / 단수형이면 Input(String) or InputNumber(StringNumber)를 붙인

      1. Input루트의 공통검증: Validator.validate~ -> 재료는 List<String> 아니면 String(Number)로 무조건 String타입을 검증대상으로 보게 정했다.

        1. 복수형이면 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);
               }
           }
          
        2. 단수형이면 String이냐 StringNumber냐에 따라서 다르게

          1. String: NullOrEmpty + IncludeBlank
          2. StringNumber: NullOrEmpty + Format(넣으면 IncludeBlank안넣어도 된다)
           private static void validateInput(String carName) {
               checkNullOrEmpty(carName);
               checkIncludeBlank(carName);
           }
                          
           public static void validateInputNumber(String inputRound) {
               checkNullOrEmpty(inputRound);
               checkValidFormat(inputRound);
           }
          
    4. 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의 존재를 알아야 하는 상황인데요, 사실 NamesCars 안에서만 사용되고 있죠. 그렇다면 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;
}
복수Dto (List<CarDto>) ex> CarsDto
private CarsDto(final List<CarDto> carsDto) {
    this.carsDto = carsDto;
}
단일Dto (원시값들) ex> CarDto
public CarDto(String name, int position) {
    this.name = name;
    this.position = position;
}

02 .fromInput ( only String or List<String> )

  • 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
//
단일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);
}
복수Dto (List<CarDto>) ex> CarsDto
  1. 일급컬렉션 내부에서 toDto()호출시 ** List<단일> **유일 필드-> List<단일Dto> 변환후 기본생성자로 복수Dto로 만들어줌 -> from일급컬렉
  2. 공통적인 기본 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
  1. 복수or일급컬렉션에서 stream에서 Car -> CarDto시 사용될 .from원본( 1:1원본)
  2. OuputView 테스트에서 원시값 필드들로 생성하는단일Dto -> 복수Dto
public CarDto(String name, int position) {
    this.name = name;
    this.position = position;
}