Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
aa3cbc6
init: 프로젝트 초기 세팅
B2SIC Jun 7, 2023
7e4578f
chore: .gitignore에 gradle 관련 내용 추가 및 불필요한 파일 제거
B2SIC Jun 7, 2023
6368d93
chore: .gitignore에 .idea 추가 및 불필요한 파일 제거
B2SIC Jun 7, 2023
a37c6cd
chore: 빌드 관련 파일 추가
B2SIC Jun 8, 2023
18c4dbc
feat: 메뉴 선택 기능 및 관련 메세지 정의 추가
B2SIC Jun 8, 2023
339bac9
feat: 연산자, 피연산자, 괄호 판별을 위한 Validator 추가
B2SIC Jun 9, 2023
2dcd810
feat: 후위표기식 변환 및 계산 기능 추가
B2SIC Jun 9, 2023
b0839b6
refactor: 실행 메인부 클래스명 변경
B2SIC Jun 9, 2023
39ff8c7
refactor: 실행 메인부 클래스명 재변경
B2SIC Jun 11, 2023
9cb8ce8
feat: 메뉴 선택 관련 입출력 기능 및 메시지 정의 추가
B2SIC Jun 11, 2023
6cbe184
refactor: 메시지 관련 클래스명 변경에 따른 기존 파일 삭제
B2SIC Jun 11, 2023
616e910
feat: 계산 이력 저장 기능 추가
B2SIC Jun 11, 2023
a119b9e
refactor: Converter 인터페이스 및 계산 기능 분리
B2SIC Jun 11, 2023
03389be
chore: 테스트를 위한 AssertJ 의존성 추가
B2SIC Jun 11, 2023
45abcdc
feat: Validator 기능 추가
B2SIC Jun 11, 2023
dd6f3bc
test: Validator 메소드 테스트
B2SIC Jun 11, 2023
5305130
test: 계산 관련 테스트
B2SIC Jun 11, 2023
2a7acd7
test: 저장소 테스트
B2SIC Jun 11, 2023
88049e0
docs: README.md 체크 리스트 수정
B2SIC Jun 11, 2023
54792dd
chore: 테스트 관련 의존성(JUnit5) 추가
B2SIC Jun 15, 2023
f3c524e
refactor: Application명 변경
B2SIC Jun 15, 2023
2cc308c
refactor: 패키지명 변경 및 static 메소드 분리, 메소드 타입 변경
B2SIC Jun 15, 2023
6fbb043
refactor: 패키지명, 클래스명 변경 및 연산식 분리, 의존성 외부 주입
B2SIC Jun 15, 2023
aae2a9e
refactor: 패키지명 변경 및 표현식 저장 타입 변경
B2SIC Jun 15, 2023
5edf79d
refactor: 연산 동작 분리를 위한 Enum 클래스 추가
B2SIC Jun 15, 2023
2464e92
refactor: 메뉴 메시지 역할 분리
B2SIC Jun 15, 2023
4e58371
refactor: Custom Exception으로 ZeroDivisionException 클래스 추가
B2SIC Jun 15, 2023
4167cfc
refactor: 클래스명 변경 및 저장소 타입 변경, 결과값 저장 기능 추가
B2SIC Jun 15, 2023
9e06b24
refactor: 클래스명 변경 및 메뉴 기능별 분리
B2SIC Jun 15, 2023
3037564
refactor: 기존 interface 메소드 -> StringUtil 클래스로 이동
B2SIC Jun 15, 2023
4f30930
fix: 두자리수 이상의 연산식을 검증 못하는 버그 수정
B2SIC Jun 15, 2023
5b23467
refactor: 클래스명 변경에 따른 테스트 클래스명 변경
B2SIC Jun 15, 2023
5dcd2aa
refactor: @ParameterizedTest 반영 및 올바른 식 검증
B2SIC Jun 15, 2023
99f6ce7
refactor: 결과 값 저장 추가에 따른 테스트 메소드 변경
B2SIC Jun 15, 2023
ac8794f
refactor: 메뉴 번호를 Enum 클래스로 관리
B2SIC Jun 17, 2023
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
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
testImplementation 'org.assertj:assertj-core:3.24.2'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.9.2'
}

test {
Expand Down
11 changes: 0 additions & 11 deletions src/main/java/App.java

This file was deleted.

13 changes: 13 additions & 0 deletions src/main/java/CalculatorApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import computation.PostfixComputer;
import computation.PostfixConverter;
import io.Calculator;
import repository.CalculatorHistoryRepository;

import java.io.IOException;

public class CalculatorApplication {
public static void main(String[] args) throws IOException {
Calculator calculator = new Calculator(new PostfixConverter(), new PostfixComputer(), new CalculatorHistoryRepository());
calculator.runApplication();
}
}
43 changes: 0 additions & 43 deletions src/main/java/calculation/Calculator.java

This file was deleted.

9 changes: 0 additions & 9 deletions src/main/java/calculation/Converter.java

This file was deleted.

67 changes: 0 additions & 67 deletions src/main/java/calculation/PostfixConverter.java

This file was deleted.

7 changes: 7 additions & 0 deletions src/main/java/computation/Converter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package computation;

import java.util.List;

public interface Converter {
List<String> convert(String expression);
}
35 changes: 35 additions & 0 deletions src/main/java/computation/Operator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package computation;

import exception.ZeroDivisionException;
import java.util.Arrays;
import java.util.function.BiFunction;

public enum Operator {
ADD("+", (num1, num2) -> num1 + num2),
SUBTRACT("-", (num1, num2) -> num1 - num2),
MULTIPLY("*", (num1, num2) -> num1 * num2),
DIVIDE("/", (num1, num2) -> {
if (num2 == 0)
throw new ZeroDivisionException();
return num1 / num2;
});

private final String operator;
private final BiFunction<Integer, Integer, Integer> expression;

Operator(String operator, BiFunction<Integer, Integer, Integer> expression) {
this.operator = operator;
this.expression = expression;
}

public static Operator getOperator(String getString) {
return Arrays.stream(Operator.values())
.filter(s -> s.operator.equals(getString))
.findFirst()
.get();
}
Comment on lines +25 to +30

Choose a reason for hiding this comment

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

getString이라는 네이밍보다는 operator라는 네이밍이 좀 더 자연스러울 것 같아요.
자바에서 변수는 명사를 주로 사용합니다!

참고자료 : https://tecoble.techcourse.co.kr/post/2020-04-24-variable_naming/


추가로 Optional.get()을 이용해서 객체를 꺼내주고 있는데요. 만약 �이상한 값이 들어오면 null이 반환될 것 같아요.
Optional에 대해 공부해보시면 좋을 것 같아요!

참고자료 : https://www.daleseo.com/java8-optional-before/

Copy link
Author

Choose a reason for hiding this comment

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

아하 그렇군요! 네이밍에 조금 더 신경을 써야 할 것 같습니다.
Optional에 대한 공부 자료도 감사합니다!


public int operate(Integer num1, Integer num2) {
return expression.apply(num1, num2);
}
}
25 changes: 25 additions & 0 deletions src/main/java/computation/PostfixComputer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package computation;

import validation.Validator;

import java.util.List;
import java.util.Stack;

public class PostfixComputer {
public int calculate(List<String> postfixExpression) {
Stack<Integer> calculationStack = new Stack<>();

for (String item : postfixExpression) {
if (Validator.isNumber(item)) {
calculationStack.push(Integer.parseInt(item));
} else if (Validator.isOperator(item)) {
Integer operand2 = calculationStack.pop();
Integer operand1 = calculationStack.pop();

Operator operator = Operator.getOperator(item);
calculationStack.push(operator.operate(operand1, operand2));
}
}
return calculationStack.pop();
}
}
76 changes: 76 additions & 0 deletions src/main/java/computation/PostfixConverter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package computation;

import validation.Validator;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Stack;

public class PostfixConverter implements Converter {
private final StringBuilder sb = new StringBuilder();

Choose a reason for hiding this comment

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

Stack자료구조와 StringBuilder를 함께 사용하고 있는데요.
굳이 StringBuilder를 사용해야 할까? 라는 생각이 드네요.
문자열을 이용하신 이유가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

convert 메소드는 입력 받은 문자열을 한글자씩 가져와서 처리를 하는데, 이 때 두 자리 이상의 숫자가 들어오는 경우
postfix 리스트에 하나의 숫자로 묶어서 저장하기 위해서 사용했습니다.
음.. StringBuilder 없이 Stack 만으로도 처리가 가능한 방법에 대해 고민해보겠습니다 🤔

private final Map<String, Integer> priority = Map.of(
"(", 0,
"+", 1,
"-", 1,
"*", 2,
"/", 2
);

private boolean checkHigherPriority(Stack<String> operatorStack, String item) {
return priority.get(operatorStack.peek()) >= priority.get(item);
}

public void addElement(List<String> list) {
if (sb.length() > 0) {
list.add(sb.toString());
sb.setLength(0);
}
}

@Override
public List<String> convert(String expression) {
List<String> postfix = new ArrayList<>();

Stack<String> operatorStack = new Stack<>();
String[] infixArray = expression.split("");
sb.setLength(0);

for (int i = 0; i < infixArray.length; i++) {
// 숫자라면 단순히 추가
if (Validator.isNumber(infixArray[i])) {
sb.append(infixArray[i]);

if (i == infixArray.length - 1) {
postfix.add(sb.toString());
}
} else {
addElement(postfix);

if (Validator.isOperator(infixArray[i])) {
// 연산자라면 먼저 우선순위 계산
if (!operatorStack.isEmpty()) {
while (!operatorStack.isEmpty() && checkHigherPriority(operatorStack, infixArray[i])) {
postfix.add(operatorStack.pop());
}
}
operatorStack.push(infixArray[i]);
} else if (Validator.isOpenBrackets(infixArray[i])) {
operatorStack.push(infixArray[i]);
} else if (Validator.isCloseBrackets(infixArray[i])) {
// 괄호에 대한 처리
while (!operatorStack.isEmpty()) {
String poppedItem = operatorStack.pop();
if (Validator.isOpenBrackets(poppedItem)) break;
postfix.add(poppedItem);
}
}
}
}

while (!operatorStack.isEmpty()) {
postfix.add(operatorStack.pop());
}
return postfix;
}
}
8 changes: 8 additions & 0 deletions src/main/java/exception/ZeroDivisionException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package exception;

public class ZeroDivisionException extends RuntimeException {
private static final String MESSAGE = "0으로는 나눌 수 없습니다.";
public ZeroDivisionException() {
super(MESSAGE);
}
}
86 changes: 86 additions & 0 deletions src/main/java/io/Calculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package io;

import computation.Converter;
import computation.PostfixComputer;
import exception.ZeroDivisionException;
import repository.Repository;
import util.StringUtil;
import validation.Validator;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Calculator {
private final BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

Choose a reason for hiding this comment

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

View객체를 따로 구현하지 않은 이유가 있을까요?? (정말 몰라서 여쭤보는 겁니다!)

Copy link
Author

Choose a reason for hiding this comment

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

특별한 이유는 없고 View 객체가 무엇을 의미하는지 조금 헷갈려서 제대로 분리하지 못한 것 같습니다 😢
여기서 View 객체의 역할을 계산기 프로그램에서 사용자와 인터렉션 하는 부분으로 지정하고 분리해보겠습니다!

private final Converter converter;
private final PostfixComputer calculator;
private final Repository repository;

public Calculator(Converter converter, PostfixComputer calculator, Repository repository) {
this.converter = converter;
this.calculator = calculator;
this.repository = repository;
}

public void printMenu() {
System.out.println(MenuMessage.SELECT.getMessage());
System.out.println(MenuMessage.CALCULATION.getMessage());
System.out.println(MenuMessage.EXIT.getMessage());
System.out.print(MenuMessage.CHOOSE.getMessage());
}

public String getUserInput() throws IOException {
return br.readLine();
}

public void runInquiry() {
System.out.println(DisplayMessage.SPLIT_LINE.getMessage());
repository.inquiry();
System.out.println(DisplayMessage.SPLIT_LINE.getMessage());
}

public void runCalculator() throws IOException {
String getInput = StringUtil.removeWhiteSpace(getUserInput());
if (!Validator.isRightExpression(getInput)) {
System.out.println(DisplayMessage.WRONG_EXPRESSION.getMessage());
return;
}

try {
int result = calculator.calculate(converter.convert(getInput));
repository.save(getInput, result);
System.out.print(DisplayMessage.OUTPUT.getMessage());
System.out.println(result);
} catch (ZeroDivisionException e) {
System.out.println(e.getMessage());
}
}


public void runApplication() throws IOException {
while (true) {
printMenu();

String getChoice = getUserInput();
if (Validator.isNotNumber(getChoice)) {
System.out.println(DisplayMessage.BAD_REQUEST.getMessage());
continue;
}

int choice = Integer.parseInt(getChoice);
if (choice == Menu.QUERY.getMenuNumber()) {
// 조회
runInquiry();
} else if (choice == Menu.CALCULATE.getMenuNumber()) {
// 계산
runCalculator();
} else if (choice == Menu.EXIT.getMenuNumber()) {
// 종료
System.exit(0);
} else {
System.out.println(DisplayMessage.BAD_REQUEST.getMessage());
}
Comment on lines +71 to +83

Choose a reason for hiding this comment

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

Menu Enum에 저장되어 있는 값을 꺼내서 비교하지 말고 입력받은 값을 Menu로 파싱한 다음
비교하면 어떨까요??
이렇게 되면 굳이 Menu를 Enum으로 관리할 필요는 없어보여요!

Copy link
Author

Choose a reason for hiding this comment

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

값을 직접 꺼내서 비교하는 것보다 입력 값을 파싱을 통해 비교하도록 하는게 Enum을 더 효과적으로 쓰는 방법이 되겠네요!
반영해보겠습니다!

}
}
}
Loading