-
Notifications
You must be signed in to change notification settings - Fork 1k
[BE 이연호] 1주차 과제 제출합니다. #1111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
[BE 이연호] 1주차 과제 제출합니다. #1111
Changes from all commits
a89c03b
9265e19
7f2a83c
61ccb3c
ca9d2a3
29305db
c22f736
9058fef
76acd14
b06d9df
28244ef
2d19816
ad712de
fa4117b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,66 @@ | ||
| # java-calculator-precourse | ||
| # java-calculator-precourse | ||
|
|
||
| # 1. 기능 요구 사항 | ||
| ## 입력한 문자열에서 숫자를 추출하여 더하는 계산기를 구현한다. | ||
|
|
||
| ### 쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환한다. | ||
| >예: "" => 0, "1,2" => 3, "1,2,3" => 6, "1,2:3" => 6 | ||
| 앞의 기본 구분자(쉼표, 콜론) 외에 커스텀 구분자를 지정할 수 있다. 커스텀 구분자는 문자열 앞부분의 "//"와 "\n" 사이에 위치하는 문자를 커스텀 구분자로 사용한다. | ||
|
|
||
| >예를 들어 "//;\n1;2;3"과 같이 값을 입력할 경우 커스텀 구분자는 세미콜론(;)이며, 결과 값은 6이 반환되어야 한다. | ||
| 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후 애플리케이션은 종료되어야 한다. | ||
|
|
||
| #### 입출력 요구 사항 | ||
| 입력 | ||
| >구분자와 양수로 구성된 문자열 | ||
|
|
||
| 출력 | ||
| >덧셈 결과 | ||
| 결과 : 6 | ||
|
|
||
| 실행 결과 예시 | ||
| >덧셈할 문자열을 입력해 주세요. | ||
| 1,2:3 | ||
| 결과 : 6 | ||
|
|
||
| # 2. 기능 목록 작성 전, 문제 핵심 파악 | ||
| ## 2.1. 핵심 알고리즘 파악 : 문자열을 구분자로 분리 | ||
| ### "String.split()" | ||
| > 간단하지만, 매번 정규식 컴파일을 하기 때문에, 대용량 기준으로 성능에 좋지 않음 | ||
|
|
||
| ### Pattern.compile(...).split() | ||
| > 초기에 정규식을 세팅해놓기는 하지만, 빠르고 정규식 재사용이 가능함. | ||
|
|
||
| ### 채택 : Pattern.compile(...).split() | ||
|
|
||
| # 3. 기능 목록 | ||
| ## 3.1. MVC 구조 분리 | ||
| ### 소스 코드가 하나로 묶이면 유지보수와 코드 가독성이 떨어짐.이를 방지하고자 코드를 분리함. | ||
| > - View : input, output 생성 | ||
| > | ||
| > - Model : input값 저장 | ||
| > | ||
| > - Service : 주요 연산 처리 | ||
| > | ||
| > - Controller : 사용자의 요청을 받아 적절한 Service를 호출하고, 처리 결과를 View에 전달함 | ||
| > 흐름 제어(요청 → 처리 → 응답) 역할 | ||
|
|
||
| ## 3.2. MVC 구조에서 기능 구현 | ||
| ### 테스트 코드를 작성하고 모델, 서비스, 컨트롤러와 뷰를 제작 | ||
| > - 모델에 임시로 고정된 값을 저장 | ||
| > - 서비스 단 제작: | ||
| >> - Service를 이용해서 모델에 사용자의 입력값을 처리 | ||
| >> - 기본 구분자를 찾아서 사용자의 입력값을 분리 | ||
| >> - 사용자 정의 구분자를 찾아서 사용자의 입력값을 분리 | ||
| >> - 구분자로 분리되어 저장된 사용자 입력값을 합계 연산하기 | ||
| >> - 입력값이 양수만 입력이 가능하도록 테스트 케이스 처리 | ||
| >> - 사용자의 입력값에서 기본 구분자와 커스텀 구분자를 함께 사용이 가능하도록 테스트 케이스 처리 | ||
| >> - 실질적으로 Model을 사용해서 구분자로 정리된 숫자 목록을 저장 후, 꺼내어 사용 | ||
|
|
||
| > - 컨트롤러 단 제작: | ||
| >> - 입력 뷰에서 입력 받은 값을 서비스에 전달 후 출력 뷰에서 출력 | ||
|
|
||
| > - 뷰 단 제작: | ||
| >> - 입력 뷰 제작 | ||
| >> - 출력 뷰 제작 | ||
| >> - 사용자 입력값에 대한 예외 사항 처리 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,11 @@ | ||
| package calculator; | ||
|
|
||
| import calculator.controller.CalculatorController; | ||
|
|
||
| public class Application { | ||
| private static final CalculatorController calculatorController = CalculatorController.getInstance(); | ||
| public static void main(String[] args) { | ||
| // TODO: 프로그램 구현 | ||
| calculatorController.calcFromString(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| package calculator.controller; | ||
|
|
||
| import calculator.service.CalculatorService; | ||
| import calculator.view.InputView; | ||
| import calculator.view.OutputView; | ||
|
|
||
| public class CalculatorController { | ||
| private InputView inputView; | ||
| private OutputView outputView; | ||
| private CalculatorService calculatorService = CalculatorService.getInstance(); | ||
|
|
||
| // start : singleton | ||
| private CalculatorController() { | ||
| inputView = new InputView(); | ||
| outputView = new OutputView(); | ||
| } | ||
|
|
||
| private static final class InnerCalculatorController { | ||
| private static final CalculatorController INSTANCE = new CalculatorController(); | ||
| } | ||
|
|
||
| public static CalculatorController getInstance() { | ||
| return InnerCalculatorController.INSTANCE; | ||
| } | ||
| // end : singleton | ||
|
|
||
|
|
||
| /** | ||
| * 사용자의 입력을 받아서, 서비스 단에 처리를 위임한 뒤, 연산 결과를 출력 뷰에 전달 | ||
| */ | ||
| public void calcFromString() { | ||
| String userInput = requestInputCalcString(); | ||
|
|
||
| // Service에서 처리 후, 결과를 받아 출력 | ||
| Integer calcResult = calculatorService.calcFromString(userInput); | ||
|
|
||
| outputView.printResult(calcResult); | ||
| } | ||
|
|
||
| /** | ||
| * 사용자에게 프로그램 안내 메시지와 입력값을 입력받아서 리턴 | ||
| * @return 사용자의 입력 String | ||
| */ | ||
| private String requestInputCalcString() { | ||
| return inputView.requestInputCalcString(); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package calculator.domain; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public class CalculatorModel { | ||
| private final List<String> userInputList; | ||
|
|
||
| public CalculatorModel(List<String> userInputList) { | ||
| this.userInputList = userInputList; | ||
| } | ||
|
|
||
| public List<String> getUserInputList() { | ||
| return userInputList; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package calculator.enums; | ||
|
|
||
| public enum UserInterfaceMsg { | ||
| CALCULATOR_INTRO("문자열과 구분자를 입력하면, 문자열에서 양수들을 추출하여 더한 값을 출력합니다.\n" | ||
| + "기본 구분자는 쉼표(,)와 콜론(:)을 가질 수 있으며,\n" | ||
| + "커스텀 구분자는 문자열 앞부분의 \"//\"와 \"\\n\" 사이에 위치하는 문자를 커스텀 구분자로 사용합니다.\n" | ||
| + "음수를 입력하면 예외를 발생시킵니다.\n" | ||
| + "덧셈할 문자열을 입력해 주세요"), | ||
| CALC_RESULT("결과 : %d") | ||
| ; | ||
|
|
||
| private String value; | ||
| UserInterfaceMsg(String value) { | ||
| this.value = value; | ||
| } | ||
|
|
||
| public String getKey() { | ||
| return name(); | ||
| } | ||
|
|
||
| public String getValue() { | ||
| return value; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| package calculator.service; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| import calculator.domain.CalculatorModel; | ||
|
|
||
| public class CalculatorService { | ||
| private CalculatorModel calculatorModel; | ||
| private CalculatorService() { | ||
| } | ||
|
|
||
| /** | ||
| * 기본 구분자 쉼표(,) 또는 콜론(:)이 사용자 입력 문자열에 있는지 확인하는 메소드 | ||
| * @param userInput | ||
| * @return Boolean | ||
| */ | ||
| public static Boolean containsDefaultDelimiters(String userInput) { | ||
| return userInput.contains(",") || userInput.contains(":"); | ||
| } | ||
|
|
||
| /** | ||
| * "//"와 "\n" 사이의 커스텀 구분자를 추출하는 메소드 | ||
| * @param userInput | ||
| * @return String | ||
| */ | ||
| public static String extractCustomDelimiter(String userInput) { | ||
| String startDelimiter = "//"; | ||
| String endDelimiter = "\\n"; | ||
|
|
||
| Integer startIndex = userInput.indexOf(startDelimiter) + startDelimiter.length(); | ||
| Integer endIndex = userInput.indexOf(endDelimiter); | ||
| if (endIndex == -1) { | ||
| // 실제 줄바꿈 문자를 인식하도록 처리 | ||
| endDelimiter = "\n"; | ||
| endIndex = userInput.indexOf(endDelimiter); | ||
| } | ||
|
|
||
| return (startIndex != -1 && endIndex != -1 && startIndex < endIndex) | ||
| ? userInput.substring(startIndex, endIndex) : ""; | ||
| } | ||
|
|
||
| /** | ||
| * 사용자로부터 입력받은 문자열 연산 | ||
| * @param userInput | ||
| * @return Integer | ||
| */ | ||
| public Integer calcFromString(String userInput) { | ||
| if (userInput.isEmpty()) { | ||
| return 0; | ||
| } | ||
| return calculateSumOfNumbers(userInput); | ||
| } | ||
|
|
||
| // 문자열을 적절한 구분자로 분리한 후 숫자의 합을 계산하는 메소드 | ||
| public Integer calculateSumOfNumbers(String userInput) { | ||
| // \\n을 실제 줄바꿈 문자로 변환 | ||
| userInput = userInput.replace("\\n", "\n"); | ||
|
|
||
| String content = userInput.substring(userInput.indexOf("\n") + 1); // 구분자 이후의 문자열 | ||
|
|
||
| // 기본 구분자 + 커스텀 구분자 모두 포함해서 처리 | ||
| String delimiter = "[,:" + extractCustomDelimiter(userInput) + "]"; | ||
| // Model 단에 사용자의 입력값을 저장 | ||
| calculatorModel = new CalculatorModel(splitByDelimiter(content, delimiter)); | ||
|
|
||
| // 숫자로 변환한 후 합계 계산 | ||
| return calculatorModel.getUserInputList().stream() | ||
| .map(Integer::parseInt) // 문자열을 Integer로 변환 | ||
| .reduce(0, Integer::sum); // 합계 | ||
| } | ||
|
|
||
| // 구분자를 기준으로 문자열을 분리하는 메소드 | ||
| public static List<String> splitByDelimiter(String userInput, String delimiter) { | ||
| return Arrays.stream(userInput.split(delimiter)) | ||
| .map(String::trim) // 공백 제거 | ||
| .peek(CalculatorService::validateNumber) // 숫자 유효성 검사 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 제가 알기론 현재처럼 중요한 검증 로직을 peek 안에 넣게 되면 의도가 잘 드러나지 않아, 오히려 validate와 같이 중요한 로직을 검증하는 것이라면, 중간에 빼서 따로 사용하는게 더 좋지 않았을까 하는 생각이 듭니다. |
||
| .collect(Collectors.toList()); // 리스트로 변환 | ||
| } | ||
|
|
||
| // 숫자 유효성 검사: 숫자가 아니거나 음수인 경우 예외 발생 | ||
| public static void validateNumber(String validateNumber) { | ||
| try { | ||
| Integer number = Integer.parseInt(validateNumber); | ||
| if (number < 0) { | ||
| throw new IllegalArgumentException("양수만 입력 가능합니다." + validateNumber); | ||
| } | ||
| } catch (NumberFormatException e) { | ||
| throw new IllegalArgumentException("숫자가 아닙니다. " + validateNumber); | ||
| } | ||
| } | ||
|
|
||
|
Comment on lines
+82
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사용자에게 입력을 받는 부분은 enum을 사용해서 처리를 해주셨던 거 같은데, 예외를 던지는 메시지 같은 경우도 상수로 처리하거나 enum을 사용하는 건 어땠을까요? 개인적으로는 통일성있는 코드 구조를 선호하다보니 읽다가 궁금증이 생겼습니다. 의도하신게 있으실까요? |
||
| private static class InnerCalculatorService { | ||
|
|
||
| private static final CalculatorService INSTANCE = new CalculatorService(); | ||
| } | ||
| public static CalculatorService getInstance(){ | ||
| return InnerCalculatorService.INSTANCE; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package calculator.view; | ||
|
|
||
| import static camp.nextstep.edu.missionutils.Console.*; | ||
|
|
||
| import calculator.enums.UserInterfaceMsg; | ||
|
|
||
| public class InputView { | ||
| public String requestInputCalcString() { | ||
| printMessage(UserInterfaceMsg.CALCULATOR_INTRO.getValue()); | ||
| return readLine(); | ||
|
|
||
| } | ||
| public void printMessage(String message) { | ||
| System.out.println(message); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package calculator.view; | ||
|
|
||
| import calculator.enums.UserInterfaceMsg; | ||
|
|
||
| public class OutputView { | ||
| public void printResult(Integer output) { | ||
| System.out.println(String.format(UserInterfaceMsg.CALC_RESULT.getValue(), output)); | ||
|
|
||
|
|
||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package calculator; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.List; | ||
|
|
||
| import calculator.domain.CalculatorModel; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| public class CalculatorModelTest { | ||
| CalculatorModel calculatorModel; | ||
| String inputString; | ||
|
|
||
| @BeforeEach | ||
| void setUp() { | ||
| inputString = "1,2:3"; | ||
| } | ||
|
|
||
| @Test | ||
| void saveUserInput() { | ||
| // given | ||
| List<String> userInputList = new ArrayList<>(Arrays.asList(inputString.split("[,:]"))); | ||
| // when | ||
| CalculatorModel calculatorModel = new CalculatorModel(userInputList); | ||
| // then | ||
| assertThat(userInputList).isEqualTo(calculatorModel.getUserInputList()); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제가 알기로는
.map(Integer::parseInt)는 Stream를 만들어서sum()를 사용할 수 없는 구조라서reduce를 쓰신 것 같습니다.Integer 스트림보다는
mapToInt(Integer::parseInt)를 활용해primitive IntStream으로 변환하면
.sum()을 바로 사용할 수 있어더 간결하고 박싱 오버헤드도 줄일 수 있을 것 같습니다.