Skip to content
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

문자열 계산기 피드백 반영 #1

Open
wants to merge 13 commits into
base: mission01
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,25 @@ git checkout main // 기본 브랜치가 main인 경우
git checkout -b 브랜치이름
ex) git checkout -b apply-feedback
```

## 구현기능
1. 문자열을 입력받는다.
- "숫자 연산자 숫자 연산자 숫자" 형식이어야 한다.
2. 입력받은 문자열을 숫자/연산자로 파싱한다.
- 연산자는 +, -, *, /로 구성된다.
3. 숫자와 연산자를 조합하여 결과를 계산한다.
- 연산자가 "+" 라면? -> a+b
- 연산자가 "-" 라면? -> a-b
- 연산자가 "x" 라면? -> axb
- 연산자가 "/" 라면? -> a/b
4. 계산된 값을 출력한다.

## 객체
- Input : 문자열을 입력 받는다.
- Output : 결과를 출력한다.
- Operator : 주어진 숫자들로 단건 연산을 진행한다.
- Operators : 연산자들을 모아놓은 일급객체
- InputNumber : 입력받은 값의 타입을 보장한다.
- InputNumbers : 입력값들을 모아놓은 일급객체
- Calculator : 계산기 로직의 진행을 담당한다.

63 changes: 63 additions & 0 deletions src/main/java/Calculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.stream.Collectors;

public class Calculator {

public static final int EVEN_NUMBER = 2;

private final String DELIMETER = " ";

private final Input input;

private final Output output;

private final Operators operators;

private final InputNumbers inputNumbers;

public Calculator(Input input, Output output, Operators operators, InputNumbers inputNumbers) {
this.input = input;
this.output = output;
this.operators = operators;
this.inputNumbers = inputNumbers;
}

public void calculate() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 메서드는 두가지 이상의 일을 하고 있어요.
메서드들이 하나의 일만 하도록 리팩토링 해보시면 좋을것 같아요.

String formula = input.inputFormula();

List<String> splittedStrings = Arrays.stream(formula.split(DELIMETER))
.collect(Collectors.toList());

validateInputFormula(splittedStrings);

Queue<String> queue = new LinkedList<>(splittedStrings);
for (int i = 0; i < splittedStrings.size(); i++) {
separateQueueData(queue, i);
}

int result = operators.operateAll(inputNumbers.convertToList());

output.showResult(result);
Comment on lines +29 to +43
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이부분도 메서드로 나뉠 수 있을 것 같아요!
최대한, 가능한! 메서드로 나누면서 메서드의 길이를 줄여주는게 좋을 것 같습니다!!

물론, 여기서까지 줄이는건 취향차이??랄가

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사실 이 부분을 어떻게 나눠야 할지 고민이 많았었는데.. 크흠
한번 해보겠습니다! ㅎㅎ

}

private void validateInputFormula(List<String> splitStrings) {
if (isEven(splitStrings.size())) {
throw new IllegalArgumentException("계산식이 올바르지 않습니다.");
}
}

private void separateQueueData(Queue<String> queue, int position) {
if (isEven(position)) {
inputNumbers.addInputNumber(queue.poll());
}
operators.addOperator(queue.poll());
}

private boolean isEven(int number) {
return (number % EVEN_NUMBER) == 0;
}

}
15 changes: 15 additions & 0 deletions src/main/java/ConsoleInput.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import java.util.Scanner;

public class ConsoleInput implements Input{

private final String REQUEST_INPUT_MESSAGE = "계산식을 입력해주세요";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OUTPUT에서는 메서드 안에서 출력값을 표기하셨는데, INPUT에서는 빼신 이유가 있을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Input에서 메시지를 밖으로 뺸 이유는 관리하기 편하려고 뻈었습니다. ㅎㅎ
Output도 통일성 있게 빼야 할 것 같아요 ㅎㅎ


private Scanner scanner = new Scanner(System.in);

@Override
public String inputFormula() {
System.out.println(REQUEST_INPUT_MESSAGE);
return scanner.nextLine();
}

}
10 changes: 10 additions & 0 deletions src/main/java/ConsoleOutput.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
public class ConsoleOutput implements Output{

public static final String RESULT_MESSAGE = "결과 = ";

@Override
public void showResult(int result) {
System.out.println(RESULT_MESSAGE + result);
}

}
5 changes: 5 additions & 0 deletions src/main/java/Input.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
public interface Input {

String inputFormula();

}
24 changes: 24 additions & 0 deletions src/main/java/InputNumber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
public class InputNumber {

private int number;

public InputNumber(String numberStr) {
validateNumberStr(numberStr);
number = Integer.parseInt(numberStr);
}

public int getNumber() {
return number;
}

private void validateNumberStr(String numberStr) {
if (numberStr.isBlank() || isNotDigit(numberStr)) {
throw new IllegalArgumentException("잘못된 형식의 숫자가 입력되었습니다.");
}
}

private boolean isNotDigit(String numberStr) {
return !Character.isDigit(numberStr.charAt(0));
}

}
11 changes: 11 additions & 0 deletions src/main/java/InputNumbers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import java.util.List;

public interface InputNumbers {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굳굳굳굳굳!!!!


void addInputNumber(String inputNumberStr);

List<Integer> convertToList();

int getLength();

}
37 changes: 37 additions & 0 deletions src/main/java/InputNumbersList.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class InputNumbersList implements InputNumbers {

private List<InputNumber> inputNumbers;

public InputNumbersList() {
inputNumbers = new ArrayList<>();
}

@Override
public void addInputNumber(String inputNumberStr) {
validateInputNumberStr(inputNumberStr);
inputNumbers.add(new InputNumber(inputNumberStr));
}

@Override
public List<Integer> convertToList() {
return inputNumbers.stream()
.map(InputNumber::getNumber)
.collect(Collectors.toList());
}

@Override
public int getLength() {
return inputNumbers.size();
}

private void validateInputNumberStr(String inputNumberStr) {
if (inputNumberStr.isEmpty()) {
throw new IllegalArgumentException("입력된 숫자가 없습니다.");
}
}

}
51 changes: 51 additions & 0 deletions src/main/java/Operator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import java.util.Arrays;

public enum Operator {
PLUS("+") {
@Override
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

람다식을 활용 해보시면 더 간결해질것 같아요 😃

public int operate(int preNumber, int postNumber) {
return preNumber + postNumber;
}
},
MINUS("-") {
@Override
public int operate(int preNumber, int postNumber) {
return preNumber - postNumber;
}
},
MULTIPLE("*") {
@Override
public int operate(int preNumber, int postNumber) {
return preNumber * postNumber;
}
},
DIVISION("/") {
@Override
public int operate(int preNumber, int postNumber) {
if (postNumber <= 0) {
throw new ArithmeticException("0 이하로는 나눌 수 없습니다.");
}
return preNumber / postNumber;
}
};

private String value;

Operator(String value) {
this.value = value;
}

public String getValue() {
return value;
}

public static Operator findOperator(String operatorStr) {
return Arrays.stream(Operator.values())
.filter(operator -> operator.getValue().equals(operatorStr))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("입력된 연산자가 부정확합니다."));
}

public abstract int operate(int preNumber, int postNumber);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굳굳굳입니다!! 추상메서드를 통해서 앞으로 enum을 더 다양한 방법으로 사용할 수 있겠어용

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사실 enum을 이렇게 사용하는 것에 대해서 고민이 많았습니다.
일단 Enum을 사용하는 객체와 강결합 되다보니 OCP 원칙을 지키기 어렵겠다는 판단에서 그냥 객체로 다 뺼까도 고민했는데...

깔끔한 코드의 유혹은 벗어나기 힘들더라구요 ㅎㅎ (Enum을 쓰는게 확실히 더 깔끔해져서 ㅎㅎ)


}
13 changes: 13 additions & 0 deletions src/main/java/Operators.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import java.util.List;

public interface Operators {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 코드에서 일급 컬렉션을 추상화 하는건 과하다는 생각이 들어요🤔
아래 주소를 참고 해보세요!!

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=complusblog&logNo=221163007357

void addOperator(String operatorStr);

Operator getNextOperator();

int operateAll(List<Integer> numbers);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

List 대신 InputNumbers를 받아야 할것 같아요 😃

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 고민하다가
Operators와 InputNumbers 간에 의존성을 없애야 할 것 같다는 생각에 이런식으로 처리하게 되었는데...

생각해보니 Operators 자체에 검증된 값(InputNumbers)이 들어오도록 강제해야 하니까
여기서는 InputNumbers가 들어가야 하는게 맞겠군요!

피드백 감사합니다!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

자료구조가 아닌 객체를 받아 이를 활용 해보시면 좋을것 같아요. 👍


int getLength();

}
50 changes: 50 additions & 0 deletions src/main/java/OperatorsQueue.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class OperatorsQueue implements Operators {

private Queue<Operator> operators;

public OperatorsQueue() {
operators = new LinkedList<>();
}

public Queue<Operator> getOperators() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

operators를 생성자에서 한번에 초기화 하면 어떨까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

원칙상 생성자에서 값을 초기화 하는게 더 안정적이겠네요 ㅎㅎ
그렇게 수정하겠습니다! 피드백 감사합니다!

return operators;
}

@Override
public void addOperator(String operatorStr) {
if (operatorStr.isEmpty()) {
throw new IllegalArgumentException("연산자가 없습니다.");
}
operators.add(Operator.findOperator(operatorStr));
}

@Override
public Operator getNextOperator() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public으로 선언된 이유가 테스트를 위해서라면 개선할 필요가 있을것 같아요!

Operator operator = operators.poll();
if (operator == null) {
throw new RuntimeException("더이상 연산자가 없습니다.");
}
return operator;
}

@Override
public int operateAll(List<Integer> numbers) {
if (numbers.isEmpty()) {
throw new IllegalArgumentException("계산할 값이 없습니다.");
}
return numbers.stream()
Copy link
Member

@darkant99 darkant99 Jan 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getNextOperator()로 operator를 가져오지 않고, operator의 stream을 열어 연산을 진행해도 될것 같아요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 또 stream을 열어버리면 코드가 좀 복잡해보이지 않을까요??

Copy link
Member

@darkant99 darkant99 Jan 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stream을 두번 열지 않고도 다른 방법이 있을것 같아요!
또한 List numbers을 InputNumbers로 변경한다면 numbers의 stream을 열기 위해서 InputNumbers에서 stream을 반환하거나, List를 반환 해야 하는데 두가지 모두 좋지 못한 상황이에요 🤔

.reduce((preNum, postNum) -> getNextOperator().operate(preNum, postNum))
.orElseThrow(() -> new RuntimeException("계산에 실패하였습니다."));
}

@Override
public int getLength() {
return operators.size();
}

}
5 changes: 5 additions & 0 deletions src/main/java/Output.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
public interface Output {

void showResult(int result);

}
13 changes: 13 additions & 0 deletions src/main/java/StringCalculatorApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
public class StringCalculatorApplication {

public static void main(String[] args) {
Input input = new ConsoleInput();
Output output = new ConsoleOutput();
Operators operators = new OperatorsQueue();
InputNumbers inputNumbers = new InputNumbersList();
Comment on lines +6 to +7
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

클래스 네이밍에 자료형이 들어가면 좋지 않습니다!
추후에 변경이나 확장이 있을 때 자료형 때문에 어려워질 수 있습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OCP 원칙을 지키기 위해 Interface를 사용했는데,
Operators의 구현체 중에 Queue를 사용해서 만든 구현체라는 의미를 주고 싶었습니다.

변경이나 확장이 필요할 경우 다른 자료구조를 사용한 객체로 갈아끼워 실제 비즈니스 로직에서의 소스변경을 없애는걸 의도했죠.
예를 들어 만약 Queue가 아닌 Stack을 사용하도록 변경할 경우 OperatorsStack이라는 클래스를 만들어서 외부에서 주입만 해주는 식으로 생각했습니다.

그래서 OperatorsQueue Class는 내부 로직의 변경이 있더라도 Queue를 사용하는 것은 변함없을 것이라 생각해서
네이밍에 자료구조를 넣게 되었습니다.

그런데 생각해보니 구현에 사용할 자료구조를 바꾸려 할 경우 새로운 Class를 만드는 것 보다
그냥 원래 구현체에서 유연하게 바꾸는게 더 좋겠군요 ㅋㅋ

깔아끼우는 것만 생각하다보니 저런식의 코딩이 된 것 같아요.

피드백 감사합니다~!
@DongGeon0908


Calculator calculator = new Calculator(input, output, operators, inputNumbers);
calculator.calculate();
}

}
26 changes: 26 additions & 0 deletions src/test/java/InputNumberTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;

class InputNumberTest {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트에 ParameterizedTest를 사용해 테스트 케이스를 늘려보세요!

private final String VALID_INPUT = "2";
private final String INVALID_INPUT = "~";

@Test
void test_constructor_success() {
InputNumber inputNumber = new InputNumber(VALID_INPUT);

assertThat(inputNumber.getNumber()).isNotNull();
assertThat(inputNumber.getNumber()).isEqualTo(Integer.parseInt(VALID_INPUT));
}

@Test
void test_constructor_invalid_input() {
assertThatThrownBy(() -> new InputNumber(INVALID_INPUT))
.isInstanceOf(IllegalArgumentException.class);
}

}
33 changes: 33 additions & 0 deletions src/test/java/InputNumbersListTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class InputNumbersListTest {

private InputNumbers inputNumbers;

@BeforeEach
void setUp() {
inputNumbers = new InputNumbersList();
}

@Test
void test_addInputNumber_success() {
String validInputNumberStr = "1";
inputNumbers.addInputNumber(validInputNumberStr);

assertThat(inputNumbers.getLength()).isEqualTo(1);
}

@Test
void test_addInputNumber_invalid_input_number_str() {
String invalidInputNumberStr = "+";

assertThatThrownBy(() -> inputNumbers.addInputNumber(invalidInputNumberStr))
.isInstanceOf(IllegalArgumentException.class);
}

}
Loading