diff --git a/docs/README.md b/docs/README.md index e69de29bb2d..6762d340859 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,47 @@ +# 구현 기능 목록 + +## 기능 + +* [x] 0부터 9 사이에 무작위 값 생성 +* [x] 무작위 값이 4 이상일 경우 전진 + +## 입력 + +* [x] 각 자동차 이름 입력 + * 이름은 5자 이하 + * 쉽표(,)를 기준 으로 자동차 이름 구분 +* [x] 자동자 이동 횟수 입력 + +## 출력 + +* [x] 각 차수별 실행 결과 출력 + * 자동차 이름과 함께 전진 하는 자동차 출력 +* [x] 최종 우승자 안내 문구 + * 공동 우승일 경우 쉼표를 이용 하여 구분 + +## 예외 사항 +* [x] 경주할 자동차 없는 경우 +* [x] 경주할 자동차 이름이 1 이상 5 이하가 아닌 경우 +* [x] 경주할 자동차 이름이 공백인 경우 +* [x] 경주할 자동차 중 중복된 이름이 있는 경우 +* [ ] 경주할 자동차 구분자 "," 가 아닌 경우 +* [x] 시도할 횟수가 자연수 아닌 경우 + +### 사용자 숫자 + +* [ ] 이름이 5자리 이하가 아닐시 예외 +* [ ] 0부터 9까지의 수가 아닐시 예외 + +## 🔨 리팩토링 목록 +* [x] 1급 Collection 적용 +* [ ] 상수 사용 +* [ ] interface 활용 (메세지 던지기) +* [x] Integer 대신 Long 사용 고려 + +## 고민해 볼 주제 +* [X] 마지막 ,로 끝나는 경우 + * 해당 경우 예외 처리 하지 않음 + * new String[]{"1", "3", }; 같은 경우도 가능 하기 때문 +* [ ] 각 차수별 실행 결과 출력의 순서 + * 사용자 입력 순 + * 사전 정렬 순 \ No newline at end of file diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e7242..754208489ee 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,17 @@ package racingcar; +import racingcar.application.RacingCarApplication; +import racingcar.controller.RacingCarController; +import racingcar.view.InputViewImpl; +import racingcar.view.OutputViewImpl; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 +// RacingCarController racingCarController = new RacingCarController(new InputViewImpl(), new OutputViewImpl()); +// racingCarController.run(); + + RacingCarApplication racingCarApplication = new RacingCarApplication(); + + racingCarApplication.run(); } } diff --git a/src/main/java/racingcar/application/RacingCarApplication.java b/src/main/java/racingcar/application/RacingCarApplication.java new file mode 100644 index 00000000000..8052a9435f0 --- /dev/null +++ b/src/main/java/racingcar/application/RacingCarApplication.java @@ -0,0 +1,18 @@ +package racingcar.application; + +import racingcar.controller.RacingCarController; +import racingcar.view.InputView; +import racingcar.view.OutputView; +import racingcar.view.InputViewImpl; +import racingcar.view.OutputViewImpl; + +public class RacingCarApplication { + public void run() { + InputView inputView = new InputViewImpl(); + OutputView outputView = new OutputViewImpl(); + + RacingCarController racingCarController = new RacingCarController(inputView, outputView); + racingCarController.run(); + } +} + diff --git a/src/main/java/racingcar/controller/RacingCarController.java b/src/main/java/racingcar/controller/RacingCarController.java new file mode 100644 index 00000000000..95c178d90d3 --- /dev/null +++ b/src/main/java/racingcar/controller/RacingCarController.java @@ -0,0 +1,66 @@ +package racingcar.controller; + +import racingcar.model.Racer; +import racingcar.model.Round; +import racingcar.view.InputView; +import racingcar.view.OutputView; + +public class RacingCarController { + private final InputView inputView; + private final OutputView outputView; + + public RacingCarController(InputView inputView, OutputView outputView){ + this.inputView = inputView; + this.outputView = outputView; + } + + public void run(){ +// String cars = inputView.readRaceCarNames(); +// String resultCars = cars.replaceAll(" ", ""); +// List names = List.of(resultCars.split(",")); + Racer racer = new Racer(inputView.readCars()); +// int round = Integer.parseInt(inputView.readRaceRound()); +// Round round = new Round(inputView.readRound()); + Round round = Round.of(inputView.readRound()); + // 각각의 racer 초기화 +// Map position = new LinkedHashMap<>(); +// for(String name: names){ +// position.put(name, 0); +// } + + outputView.printExecutionResult(); + // 라운드 별 각각의 레이서 결과 출력 + while(round.hasRound()){ + play(racer); +// racer.play(); +// outputView.printResult(racer.toString()); + round.turn(); + } +// for (int i=0 ; i < round ; i++){ +// racer.play(); +// outputView.printResult(racer); +// play(position); +// outputView.printResult(position); +// System.out.println(); +// } + + outputView.printFinalWinner(racer.winnerToString()); + } + + private void play(Racer racer){ + racer.play(); + outputView.printResult(racer.toString()); + } + +// private void play(Map position) { +// for (String name : position.keySet()){ +// if (ShiftGear.moveForward()) { +// playMoveForward(position, name); +// } +// } +// } + +// private void playMoveForward(Map position, String name){ +// position.put(name, position.get(name) + 1); +// } +} diff --git a/src/main/java/racingcar/model/Racer.java b/src/main/java/racingcar/model/Racer.java new file mode 100644 index 00000000000..9c8281e46f0 --- /dev/null +++ b/src/main/java/racingcar/model/Racer.java @@ -0,0 +1,117 @@ +package racingcar.model; + +import racingcar.model.car.Car; +import racingcar.model.car.OrderByPosition; +import racingcar.model.car.OrderStrategy; +import racingcar.validation.Validator; +import racingcar.validation.ValidatorFactory; +import static racingcar.type.message.MessageType.NAME_SEPARATOR; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class Racer { +// public static final String SEPERATOR = ","; + private final List racer; + public Racer(String name) { + validate(name); + this.racer = Arrays.stream(name.split(NAME_SEPARATOR.getMessageValue())) + .map(Car::ofStartPoint) + .toList(); + } + + public void play(){ + racer.forEach(Car::move); + } + + private void validate(String value){ + ValidatorFactory validatorFactory = ValidatorFactory.buildDefaultValidatorFactory(); + Validator validator = validatorFactory.getValidator(this.getClass()); + validator.validate(value); +// Validator validator = new RacerValidator(); +// validator.validate(value); + } + +// public String getWinner() { +// Car winner = racer.stream().max(Car::compareTo).orElseThrow(); +// +// return racer.stream() +// .filter(winner::equals) +// .map(Car::getName) +// .collect(Collectors.joining(SEPERATOR)); +// } + +// public List getWinner() { +// Car first = racer.stream() +// .max(Car::compareTo) +// .orElseThrow(IllegalAccessError::new); +// +// return racer.stream() +// .filter(car -> car.equals(first)) +// .toList(); +// } + + public List getWinner(OrderStrategy orderStrategy){ + Car first = racer.stream() + .max(orderStrategy) + .orElseThrow(); + return racer.stream() + .filter(car -> car.equals(first)) + .toList(); + } + + public String winnerToString() { + return getWinner(new OrderByPosition()) + .stream() + .map(Car::getName) + .collect(Collectors.joining(NAME_SEPARATOR + " ")); + +// List winner = getWinner(new OrderByPosition()) +// .stream() +// .map(Car::getName) +// .toList(); +// +// return String.join(NAME_SEPARATOR + " ", winner); + +// List winner = getWinner(); +// StringBuilder stringBuilder = new StringBuilder(); +// for (Car car : winner){ +// stringBuilder.append(car.getName()).append(","); +// } +// stringBuilder.deleteCharAt(stringBuilder.length() - 1); +// return stringBuilder.toString(); + } + + @Override + public String toString(){ + StringBuilder stringBuilder = new StringBuilder(); + for (Car car : racer){ + stringBuilder.append(car.toString()).append("\n"); + } + return stringBuilder.toString(); + } + + @Override + public boolean equals(Object obj){ + if (this == obj){ + return true; + } + + if (!(obj instanceof Racer target)){ + return false; + } + + return racer.equals(target.racer); + } + +// private void validate(String name){ +// validateSize(name); +// } +// +// private void validateSize(String value){ +// if (value == null || value.split(SEPERATOR).length == 0){ +// throw new IllegalArgumentException("참가자 없음"); +// } +// } +} diff --git a/src/main/java/racingcar/model/Round.java b/src/main/java/racingcar/model/Round.java new file mode 100644 index 00000000000..46bf60bb80a --- /dev/null +++ b/src/main/java/racingcar/model/Round.java @@ -0,0 +1,61 @@ +package racingcar.model; + +import racingcar.validation.RoundValidator; +import racingcar.validation.Validator; +import racingcar.validation.ValidatorFactory; + +import static racingcar.type.PlayType.MAX_NUM; +import static racingcar.type.PlayType.MIN_NUM; + +public class Round { + private Long round; +// private Integer round; + + private Round(String round){ + validate(round); + this.round = Long.valueOf(round); + } + + public static Round of(String round){ + return new Round(round); + } + + // TODO: 진행 중인지 확인 하는 것 뿐 아니라 감소도 함 +// public Boolean isContinue(){ +// if (round > 0){ +// round --; +// return true; +// } +// return false; +// } + public Boolean hasRound(){ +// turn(); + return round >= 0; + } + + public void turn() { + round--; + } + + private void validate(String value){ + ValidatorFactory validatorFactory = ValidatorFactory.buildDefaultValidatorFactory(); + Validator validator = validatorFactory.getValidator(this.getClass()); + validator.validate(value); +// Validator validator = new RoundValidator(); +// validator.validate(value); +// validateType(value); +// validateRange(value); + } + +// private void validateType(String value){ +// if (value != null && !value.matches(ONLY_NUMBER)){ +// throw new IllegalArgumentException(); +// } +// } +// +// private void validateRange(String value){ +// if (0 >= Integer.parseInt(value)) { +// throw new IllegalArgumentException(); +// } +// } +} diff --git a/src/main/java/racingcar/model/car/Car.java b/src/main/java/racingcar/model/car/Car.java new file mode 100644 index 00000000000..9fe92c37e58 --- /dev/null +++ b/src/main/java/racingcar/model/car/Car.java @@ -0,0 +1,86 @@ +package racingcar.model.car; + +import racingcar.service.ShiftGear; +import racingcar.validation.Validator; +import racingcar.validation.ValidatorFactory; + +import static racingcar.type.message.MessageType.MARK; + +// TODO : 자동차 경주에 말이 들어 온다면? +public class Car { +// private static final String MARK = "-"; + // TODO : 어떻게 선언 할까 +// private final Comparator comparator = new OrderByPosition(); + + protected final String name; + protected Long currentPosition; + + private Car(String name, Long currentPosition){ + validate(name); + this.name = name; + this.currentPosition = currentPosition; + } + + public static Car ofStartPoint(String name){ + return new Car(name, 0L); + } + + public void move() { + if (ShiftGear.moveForward()){ + currentPosition++; + } + } + + private void validate(String value){ + // TODO : 다른 방식으로 구현체 가져오기 + ValidatorFactory validatorFactory = ValidatorFactory.buildDefaultValidatorFactory(); + Validator validator = validatorFactory.getValidator(this.getClass()); + validator.validate(value); +// Validator carValidator = new CarValidator(); +// if(carValidator.support(Car.class)) { +// carValidator.validate(value); +// } + } + + public String getName() { + return this.name; + } + + @Override + public String toString(){ + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(this.name).append(" : "); + for (long i=0 ; i < this.currentPosition ; i++){ + stringBuilder.append(MARK); + } + return stringBuilder.toString(); +// return this.name + " : " + MARK.repeat(this.currentPosition); + } + + @Override + public boolean equals(Object obj){ + if (this == obj){ + return true; + } + + if(!(obj instanceof Car car)){ + return false; + } + return this.currentPosition.equals(car.currentPosition); + } + +// public int compareTo(Car car) { +// return comparator.compare(this, car); +//// return this.currentPosition - car.currentPosition; +// } + +// private void validateName(String name){ +// validateLength(name); +// } +// +// private void validateLength(String value){ +// if(MIN_LENGTH.getPlayValue() > value.length() || value.length() > MAX_LENGTH.getPlayValue()){ +// throw new IllegalArgumentException(); +// } +// } +} diff --git a/src/main/java/racingcar/model/car/OrderByPosition.java b/src/main/java/racingcar/model/car/OrderByPosition.java new file mode 100644 index 00000000000..3e47c43f1a0 --- /dev/null +++ b/src/main/java/racingcar/model/car/OrderByPosition.java @@ -0,0 +1,8 @@ +package racingcar.model.car; + +public class OrderByPosition implements OrderStrategy { + @Override + public int compare(Car o1, Car o2) { + return o1.currentPosition.compareTo(o2.currentPosition); + } +} diff --git a/src/main/java/racingcar/model/car/OrderStrategy.java b/src/main/java/racingcar/model/car/OrderStrategy.java new file mode 100644 index 00000000000..71f7750cef1 --- /dev/null +++ b/src/main/java/racingcar/model/car/OrderStrategy.java @@ -0,0 +1,8 @@ +package racingcar.model.car; + +import java.util.Comparator; + +public interface OrderStrategy extends Comparator { + @Override + int compare(Car o1, Car o2); +} diff --git a/src/main/java/racingcar/service/JudgeWinner.java b/src/main/java/racingcar/service/JudgeWinner.java new file mode 100644 index 00000000000..2b8c0e1a55e --- /dev/null +++ b/src/main/java/racingcar/service/JudgeWinner.java @@ -0,0 +1,26 @@ +package racingcar.service; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +//public class JudgeWinner { +// private JudgeWinner() {} +// +// public static List chooseWinner(Map position){ +// int winnerPosition = calculatePosition(position); +// return position.keySet().stream() +// .filter(name -> isWinner(position.get(name), winnerPosition)) +// .collect(Collectors.toList()); +// } +// +// private static Integer calculatePosition(Map position){ +// return position.values().stream() +// .max(Integer::compare) +// .orElse(0); +// } +// +// private static boolean isWinner(int racerPosition, int winnerPosition){ +// return racerPosition == winnerPosition; +// } +//} diff --git a/src/main/java/racingcar/service/NumberGenerator.java b/src/main/java/racingcar/service/NumberGenerator.java new file mode 100644 index 00000000000..3053505449b --- /dev/null +++ b/src/main/java/racingcar/service/NumberGenerator.java @@ -0,0 +1,5 @@ +package racingcar.service; + +public interface NumberGenerator { + int generate(); +} diff --git a/src/main/java/racingcar/service/RandomNumberGenerator.java b/src/main/java/racingcar/service/RandomNumberGenerator.java new file mode 100644 index 00000000000..77fe4460315 --- /dev/null +++ b/src/main/java/racingcar/service/RandomNumberGenerator.java @@ -0,0 +1,13 @@ +package racingcar.service; + +import camp.nextstep.edu.missionutils.Randoms; + +import static racingcar.type.PlayType.MAX_NUM; +import static racingcar.type.PlayType.MIN_NUM; + +public class RandomNumberGenerator implements NumberGenerator{ + @Override + public int generate() { + return Randoms.pickNumberInRange(MIN_NUM.getPlayValue(), MAX_NUM.getPlayValue()); + } +} diff --git a/src/main/java/racingcar/service/ShiftGear.java b/src/main/java/racingcar/service/ShiftGear.java new file mode 100644 index 00000000000..3daf104ff55 --- /dev/null +++ b/src/main/java/racingcar/service/ShiftGear.java @@ -0,0 +1,11 @@ +package racingcar.service; + +import racingcar.util.PickRandomNumber; + +import static racingcar.type.PlayType.MOVING_POSSIBILITY; + +public class ShiftGear { + public static boolean moveForward() { + return PickRandomNumber.generate() >= MOVING_POSSIBILITY.getPlayValue(); + } +} diff --git a/src/main/java/racingcar/type/PlayType.java b/src/main/java/racingcar/type/PlayType.java new file mode 100644 index 00000000000..34e45d749e4 --- /dev/null +++ b/src/main/java/racingcar/type/PlayType.java @@ -0,0 +1,16 @@ +package racingcar.type; + +public enum PlayType { + MAX_LENGTH(1), + MIN_LENGTH(5), + MAX_NUM(9), + MIN_NUM(0), + MOVING_POSSIBILITY(4), + MIN_PARTICIPANT(1); + + private final int playValue; + + PlayType(int playValue) { this.playValue = playValue; } + + public int getPlayValue() { return playValue; } +} diff --git a/src/main/java/racingcar/type/message/ErrorMessageType.java b/src/main/java/racingcar/type/message/ErrorMessageType.java new file mode 100644 index 00000000000..3a0360f7e65 --- /dev/null +++ b/src/main/java/racingcar/type/message/ErrorMessageType.java @@ -0,0 +1,16 @@ +package racingcar.type.message; +import static racingcar.type.PlayType.*; +public enum ErrorMessageType { + INVALID_LENGTH(String.format("이름의 길이는 %s 이상 %s 이하여야 합니다.", MIN_LENGTH, MAX_LENGTH)), + BLANK_SPACE("이름은 공백이 아니어야 합니다."), + NO_PARTICIPANT("참가자가 없습니다."), + INVALID_TYPE("잘못된 타입 입니다."), + INVALID_RANGE("잘못된 범위 입니다."), + INVALID_UNIQUE("중복 입니다."); + + private final String errorMessageValue; + + ErrorMessageType(String errorMessageValue) { this.errorMessageValue = errorMessageValue; } + + public String getErrorMessageValue() { return errorMessageValue; } +} diff --git a/src/main/java/racingcar/type/message/MessageType.java b/src/main/java/racingcar/type/message/MessageType.java new file mode 100644 index 00000000000..746743d6210 --- /dev/null +++ b/src/main/java/racingcar/type/message/MessageType.java @@ -0,0 +1,17 @@ +package racingcar.type.message; + +public enum MessageType { + MARK("-"), + NAME_SEPARATOR(","), + + ENTER_CAR_NAME("경주할 자동차 이름을 입력 하세요.(이름은 쉼표(,) 기준 으로 구분)"), + ENTER_RACE_ROUND("시도할 회수는 몇 회 인가요"), + ENTER_RACE_RESULT("\n실행 결과"), + FINAL_WINNER("최종 우승자 : %s"); + + private final String messageValue; + + MessageType(String messageValue) { this.messageValue = messageValue; } + + public String getMessageValue() { return messageValue; } +} diff --git a/src/main/java/racingcar/util/PickRandomNumber.java b/src/main/java/racingcar/util/PickRandomNumber.java new file mode 100644 index 00000000000..9c8458c6af2 --- /dev/null +++ b/src/main/java/racingcar/util/PickRandomNumber.java @@ -0,0 +1,14 @@ +package racingcar.util; + +import camp.nextstep.edu.missionutils.Randoms; + +import static racingcar.type.PlayType.MAX_NUM; +import static racingcar.type.PlayType.MIN_NUM; + +public class PickRandomNumber { + public PickRandomNumber(){} + + public static Integer generate(){ + return Randoms.pickNumberInRange(MIN_NUM.getPlayValue(), MAX_NUM.getPlayValue()); + } +} diff --git a/src/main/java/racingcar/validation/CarValidator.java b/src/main/java/racingcar/validation/CarValidator.java new file mode 100644 index 00000000000..495652a4d2a --- /dev/null +++ b/src/main/java/racingcar/validation/CarValidator.java @@ -0,0 +1,31 @@ +package racingcar.validation; + +import racingcar.model.car.Car; + +import static racingcar.type.PlayType.*; +import static racingcar.type.message.ErrorMessageType.*; + +public class CarValidator implements Validator { + @Override + public boolean support(Class clazz) { + return Car.class.isAssignableFrom(clazz); + } + + @Override + public void validate(Object target) { + validateLength((String) target); + validateSpace((String) target); + } + + private void validateLength(String value){ + if(MIN_LENGTH.getPlayValue() > value.length() || value.length() > MAX_LENGTH.getPlayValue()){ + throw new IllegalArgumentException(INVALID_LENGTH.getErrorMessageValue()); + } + } + + private void validateSpace(String value){ + if (value.trim().equals("")){ + throw new IllegalArgumentException(BLANK_SPACE.getErrorMessageValue()); + } + } +} diff --git a/src/main/java/racingcar/validation/RacerValidator.java b/src/main/java/racingcar/validation/RacerValidator.java new file mode 100644 index 00000000000..5f604af58ae --- /dev/null +++ b/src/main/java/racingcar/validation/RacerValidator.java @@ -0,0 +1,35 @@ +package racingcar.validation; + +import racingcar.model.Racer; + +import java.util.Set; + +import static racingcar.type.PlayType.MIN_PARTICIPANT; +import static racingcar.type.message.ErrorMessageType.INVALID_UNIQUE; +import static racingcar.type.message.MessageType.NAME_SEPARATOR; +import static racingcar.type.message.ErrorMessageType.NO_PARTICIPANT; +public class RacerValidator implements Validator { +// public static final String SEPERATOR = ","; + @Override + public boolean support(Class clazz) { + return Racer.class.isAssignableFrom(clazz); + } + + @Override + public void validate(Object target) { + validateSize((String) target); + validateUnique((String) target); + } + + public void validateSize(String value){ + if (value == null || value.split(NAME_SEPARATOR.getMessageValue()).length < MIN_PARTICIPANT.getPlayValue()) { + throw new IllegalArgumentException(NO_PARTICIPANT.getErrorMessageValue()); + } + } + + public void validateUnique(String value){ + if (Set.of(value.split(NAME_SEPARATOR.getMessageValue())).size() != value.split(NAME_SEPARATOR.getMessageValue()).length){ + throw new IllegalArgumentException(INVALID_UNIQUE.getErrorMessageValue()); + } + } +} diff --git a/src/main/java/racingcar/validation/RoundValidator.java b/src/main/java/racingcar/validation/RoundValidator.java new file mode 100644 index 00000000000..111d0210baf --- /dev/null +++ b/src/main/java/racingcar/validation/RoundValidator.java @@ -0,0 +1,38 @@ +package racingcar.validation; + +import racingcar.model.Round; + +import java.util.regex.Pattern; + +import static racingcar.type.PlayType.MAX_NUM; +import static racingcar.type.PlayType.MIN_NUM; +import static racingcar.type.message.ErrorMessageType.INVALID_TYPE; +import static racingcar.type.message.ErrorMessageType.INVALID_RANGE; + +public class RoundValidator implements Validator { + public static final String VALID_RANGE = "^["+ MIN_NUM + "-" + MAX_NUM +"]+"; + private static final Pattern NUMBER = Pattern.compile(VALID_RANGE); + @Override + public boolean support(Class clazz) { + return Round.class.isAssignableFrom(clazz); + } + + @Override + public void validate(Object target) { + validateType((String) target); + validateRange((String) target); + } + + private void validateType(String value){ +// if (value != null && !value.matches(VALID_RANGE)){ + if (value != null & !NUMBER.matcher(value).matches()){ + throw new IllegalArgumentException(INVALID_TYPE.getErrorMessageValue()); + } + } + + private void validateRange(String value){ + if (0 > Integer.parseInt(value)) { + throw new IllegalArgumentException(INVALID_RANGE.getErrorMessageValue()); + } + } +} diff --git a/src/main/java/racingcar/validation/Validator.java b/src/main/java/racingcar/validation/Validator.java new file mode 100644 index 00000000000..fd49b1a1e50 --- /dev/null +++ b/src/main/java/racingcar/validation/Validator.java @@ -0,0 +1,7 @@ +package racingcar.validation; + +public interface Validator { + boolean support(Class clazz); + + void validate(Object target); +} diff --git a/src/main/java/racingcar/validation/ValidatorFactory.java b/src/main/java/racingcar/validation/ValidatorFactory.java new file mode 100644 index 00000000000..ad07a2c0124 --- /dev/null +++ b/src/main/java/racingcar/validation/ValidatorFactory.java @@ -0,0 +1,36 @@ +package racingcar.validation; + +import java.util.HashSet; +import java.util.Set; + +public class ValidatorFactory { + private static ValidatorFactory validatorFactory; + private final Set validators = new HashSet<>(); + private ValidatorFactory() {} + + public static ValidatorFactory buildDefaultValidatorFactory() { + if (validatorFactory != null){ + return validatorFactory; + } + + validatorFactory = new ValidatorFactory(); + validatorFactory.registerValidator(new CarValidator()); + validatorFactory.registerValidator(new RoundValidator()); + validatorFactory.registerValidator(new RacerValidator()); + + return validatorFactory; + } + + private void registerValidator(Validator validator) { + validators.add(validator); + } + + public Validator getValidator(Class clazz) { + for (Validator validator : validators){ + if (validator.support(clazz)){ + return validator; + } + } + throw new IllegalArgumentException(); + } +} diff --git a/src/main/java/racingcar/view/InputView.java b/src/main/java/racingcar/view/InputView.java new file mode 100644 index 00000000000..10a92cdb315 --- /dev/null +++ b/src/main/java/racingcar/view/InputView.java @@ -0,0 +1,24 @@ +package racingcar.view; + +public interface InputView { + String readCars(); + + String readRound(); + +// private static final String ENTER_CAR_NAME = "경주할 자동차 이름을 입력 하세요.(이름은 쉼표(,) 기준 으로 구분)"; +// private static final String ENTER_RACE_ROUND = "시도할 회수는 몇 회 인가요?"; +// +// public String readRaceCarNames() { +// System.out.println(ENTER_CAR_NAME); +// return readLine(); +// } +// +// public String readRaceRound() { +// System.out.println(ENTER_RACE_ROUND); +// return readLine(); +// } +// +// protected String readLine(){ +// return Console.readLine(); +// } +} diff --git a/src/main/java/racingcar/view/InputViewImpl.java b/src/main/java/racingcar/view/InputViewImpl.java new file mode 100644 index 00000000000..0a55578df0e --- /dev/null +++ b/src/main/java/racingcar/view/InputViewImpl.java @@ -0,0 +1,26 @@ +package racingcar.view; + +import camp.nextstep.edu.missionutils.Console; + +import static racingcar.type.message.MessageType.ENTER_CAR_NAME; +import static racingcar.type.message.MessageType.ENTER_RACE_ROUND; + +public class InputViewImpl implements InputView { +// private static final String ENTER_CAR_NAME = "경주할 자동차 이름을 입력 하세요.(이름은 쉼표(,) 기준 으로 구분)"; +// private static final String ENTER_RACE_ROUND = "시도할 회수는 몇 회 인가요?"; + @Override + public String readCars() { + System.out.println(ENTER_CAR_NAME); + return readLine(); + } + + @Override + public String readRound() { + System.out.println(ENTER_RACE_ROUND); + return readLine(); + } + + protected String readLine() { + return Console.readLine(); + } +} diff --git a/src/main/java/racingcar/view/OutputView.java b/src/main/java/racingcar/view/OutputView.java new file mode 100644 index 00000000000..da4d724fc28 --- /dev/null +++ b/src/main/java/racingcar/view/OutputView.java @@ -0,0 +1,29 @@ +package racingcar.view; + +public interface OutputView { + void printExecutionResult(); + + void printResult(String result); + + void printFinalWinner(String winner); + +// private static final String ENTER_RACE_RESULT = "실행 결과"; +// public void printExecution() { +// System.out.println(ENTER_RACE_RESULT); +// } +// +// public void printResult(Racer racer){ +//// for (String name : position.keySet()){ +//// System.out.println(name + " : " + "-".repeat(position.get(name))); +//// } +// System.out.println(racer.toString()); +// } +// +// public void printWinner(String winner){ +// System.out.println("최종 우승자 : " + winner); +// } +// +//// private String formatted(List winner){ +//// return String.join(", ", winner); +//// } +} diff --git a/src/main/java/racingcar/view/OutputViewImpl.java b/src/main/java/racingcar/view/OutputViewImpl.java new file mode 100644 index 00000000000..e44a7fef92f --- /dev/null +++ b/src/main/java/racingcar/view/OutputViewImpl.java @@ -0,0 +1,22 @@ +package racingcar.view; + +import static racingcar.type.message.MessageType.ENTER_RACE_RESULT; +import static racingcar.type.message.MessageType.FINAL_WINNER; + +public class OutputViewImpl implements OutputView { +// private static final String ENTER_RACE_RESULT = "\n실행 결과"; + @Override + public void printExecutionResult() { + System.out.println(ENTER_RACE_RESULT); + } + + @Override + public void printResult(String result) { + System.out.println(result); + } + + @Override + public void printFinalWinner(String winner) { + System.out.printf(FINAL_WINNER.getMessageValue(), winner); + } +} diff --git a/src/test/java/racingcar/model/CarTest.java b/src/test/java/racingcar/model/CarTest.java new file mode 100644 index 00000000000..d07f7e6d2ff --- /dev/null +++ b/src/test/java/racingcar/model/CarTest.java @@ -0,0 +1,18 @@ +package racingcar.model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import racingcar.model.car.Car; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class CarTest { + @DisplayName("자동차 이름의 길이가 잘못된 경우 예외 발생") + @ParameterizedTest(name = "{displayName} value = {0}") + @ValueSource(strings = {"myCarIsKia", "myName", " "}) + void checkValidateLength(String name){ + assertThatThrownBy(() -> Car.ofStartPoint(name)) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/racingcar/model/RacerTest.java b/src/test/java/racingcar/model/RacerTest.java new file mode 100644 index 00000000000..51dc9546b52 --- /dev/null +++ b/src/test/java/racingcar/model/RacerTest.java @@ -0,0 +1,27 @@ +package racingcar.model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class RacerTest { + @DisplayName("참가자가 없는 경우 예외 발생") + @ParameterizedTest + @NullSource + @ValueSource(strings = {"asdf"}) + void checkRacer(String value){ + assertThatThrownBy(() -> new Racer(value)) + .isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("중복된 이름 체크") + @ParameterizedTest + @ValueSource(strings = {"ad, k, la, a, la", "l, l", "qwe, kz, pi, pi"}) + void checkUnique(String value){ + assertThatThrownBy(() -> new Racer(value)) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/racingcar/model/RoundTest.java b/src/test/java/racingcar/model/RoundTest.java new file mode 100644 index 00000000000..34b4dac2d7e --- /dev/null +++ b/src/test/java/racingcar/model/RoundTest.java @@ -0,0 +1,40 @@ +package racingcar.model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class RoundTest { + @DisplayName("잘못된 횟수 입력 테스트") + @ParameterizedTest(name = "{displayName}: {0}") + @ValueSource(strings = {"-123", "akd", "", "-", "12a", " 1"}) + @NullSource + void checkInvalidRound(String value){ + assertThatThrownBy(() -> + Round.of(value)).isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("정상 횟수 입력 테스트") + @ParameterizedTest(name = "{displayName}: {0}") + @ValueSource(strings = {"120", "2", "2158249", "3213095803"}) + void checkValidRound(String value){ + assertThat(Round.of(value).hasRound()).isTrue(); + } + + @DisplayName("라운드 플레이 테스트") + @ParameterizedTest(name = "{displayName}: {0}") + @ValueSource(strings = {"3", "5", "100", "0"}) + void checkRoundPlay(String value){ + Round round = Round.of(value); + long count = Long.parseLong(value); + while(round.hasRound()) { + round.turn(); + count--; + } + assertThat(count).isEqualTo(-1); + } +} diff --git a/src/test/java/racingcar/util/PickRandomNumberTest.java b/src/test/java/racingcar/util/PickRandomNumberTest.java new file mode 100644 index 00000000000..93a81e6b4e8 --- /dev/null +++ b/src/test/java/racingcar/util/PickRandomNumberTest.java @@ -0,0 +1,16 @@ +package racingcar.util; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static racingcar.type.PlayType.MAX_NUM; +import static racingcar.type.PlayType.MIN_NUM; + +public class PickRandomNumberTest { + @DisplayName("올바른 범위의 랜덤한 숫자 생성 테스트") + @Test + void checkPickRandomNumber() { + assertThat(PickRandomNumber.generate()).isBetween(MIN_NUM.getPlayValue(), MAX_NUM.getPlayValue()); + } +} diff --git a/src/test/java/racingcar/util/RandomNumberGeneratorTest.java b/src/test/java/racingcar/util/RandomNumberGeneratorTest.java new file mode 100644 index 00000000000..443bb901f49 --- /dev/null +++ b/src/test/java/racingcar/util/RandomNumberGeneratorTest.java @@ -0,0 +1,19 @@ +package racingcar.util; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import racingcar.service.RandomNumberGenerator; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RandomNumberGeneratorTest { + @DisplayName("올바른 범위의 랜덤한 숫자 생성") + @Test + void checkRandomNumberGenerate(){ + RandomNumberGenerator numberGenerator = new RandomNumberGenerator(); + int t = 10; + while (t --> 0){ + assertThat(numberGenerator.generate()).isBetween(0,9); + } + } +} diff --git a/src/test/java/racingcar/validation/CarValidatorTest.java b/src/test/java/racingcar/validation/CarValidatorTest.java new file mode 100644 index 00000000000..a976c2be3fb --- /dev/null +++ b/src/test/java/racingcar/validation/CarValidatorTest.java @@ -0,0 +1,18 @@ +package racingcar.validation; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import racingcar.model.car.Car; + +import static org.assertj.core.api.Assertions.assertThat; + +class CarValidatorTest { +// private final CarValidator carValidator = new CarValidator(); + + @DisplayName("클래스 지원 테스트") + @Test + void checkSupport() { + Validator carValidator = new CarValidator(); + assertThat(carValidator.support(Car.class)).isTrue(); + } +} diff --git a/src/test/java/racingcar/validation/RacerValidatorTest.java b/src/test/java/racingcar/validation/RacerValidatorTest.java new file mode 100644 index 00000000000..3a843f9d384 --- /dev/null +++ b/src/test/java/racingcar/validation/RacerValidatorTest.java @@ -0,0 +1,17 @@ +package racingcar.validation; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import racingcar.model.Racer; + +import static org.assertj.core.api.Assertions.assertThat; + +class RacerValidatorTest { + // TODO: 추후 검증기 모아 한 클래스 에서 테스트 + @DisplayName("클래스 지원 테스트") + @Test + void checkSupport(){ + Validator validator = new RacerValidator(); + assertThat(validator.support(Racer.class)).isTrue(); + } +} diff --git a/src/test/java/racingcar/validation/RoundValidatorTest.java b/src/test/java/racingcar/validation/RoundValidatorTest.java new file mode 100644 index 00000000000..b09062b6221 --- /dev/null +++ b/src/test/java/racingcar/validation/RoundValidatorTest.java @@ -0,0 +1,15 @@ +package racingcar.validation; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import racingcar.model.Round; + +import static org.assertj.core.api.Assertions.assertThat; +class RoundValidatorTest { + @DisplayName("클래스 지원 테스트") + @Test + void checkSupport() { + Validator validator = new RoundValidator(); + assertThat(validator.support(Round.class)).isTrue(); + } +} diff --git a/src/test/java/racingcar/validation/ValidatorFactoryTest.java b/src/test/java/racingcar/validation/ValidatorFactoryTest.java new file mode 100644 index 00000000000..0a5e60893b2 --- /dev/null +++ b/src/test/java/racingcar/validation/ValidatorFactoryTest.java @@ -0,0 +1,33 @@ +package racingcar.validation; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import racingcar.model.Racer; +import racingcar.model.Round; +import racingcar.model.car.Car; + +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ValidatorFactoryTest { + + @DisplayName("클래스에 맞는 검증기 가져오는지 테스트") + @ParameterizedTest(name = "{index} {0}, {1}") + @MethodSource("checkValidatorParametersProvider") + void checkValidatorFactory(Class clazz, Validator validator){ + ValidatorFactory validatorFactory = ValidatorFactory.buildDefaultValidatorFactory(); + + assertThat(validatorFactory.getValidator(clazz)).isInstanceOf(validator.getClass()); + } + + static Stream checkValidatorParametersProvider() { + return Stream.of( + Arguments.arguments(Car.class, new CarValidator()), + Arguments.arguments(Racer.class, new RacerValidator()), + Arguments.arguments(Round.class, new RoundValidator()) + ); + } +}