-
Notifications
You must be signed in to change notification settings - Fork 156
[4기 -유원우] 1~2주차 과제 : 계산기 구현 미션 제출합니다. #167
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: wonu606
Are you sure you want to change the base?
Changes from all commits
6fa2360
490ce1d
728fcad
b99177b
bac3c9f
463f199
8a83609
72db880
8f347a2
95306f6
400b2a4
60a1eb4
3dfc78e
ef0ddd8
52ddf84
7bba45d
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,21 +1,55 @@ | ||
package com.wonu606.calculator; | ||
|
||
import com.wonu606.app.App; | ||
import com.wonu606.calculator.calculator.PostfixCalculator; | ||
import com.wonu606.calculator.converter.InfixToPostfixConverter; | ||
import com.wonu606.calculator.storage.Persistence; | ||
import com.wonu606.calculator.storage.ResultStore; | ||
import com.wonu606.calculator.strategy.CalculationStrategy; | ||
import com.wonu606.calculator.strategy.CalculatorStrategy; | ||
import com.wonu606.calculator.util.CalculatorMessage; | ||
import com.wonu606.calculator.validator.InfixValidator; | ||
import com.wonu606.io.Input; | ||
import com.wonu606.io.Print; | ||
import java.io.IOException; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
public class CalculatorApp implements App { | ||
|
||
Input input; | ||
Print printer; | ||
private final Map<String, CalculatorStrategy> strategies = new HashMap(); | ||
private final Persistence store = new ResultStore(); | ||
|
||
public void execute(Input input, Print printer) throws IOException { | ||
this.input = input; | ||
this.printer = printer; | ||
public CalculatorApp() { | ||
initStrategies(); | ||
} | ||
|
||
private void initStrategies() { | ||
strategies.put("1", new CalculationStrategy( | ||
new InfixValidator(), new InfixToPostfixConverter(), new PostfixCalculator())); | ||
Comment on lines
+27
to
+28
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. 구현체에 직접 의존하게 된다면,� 객체지향의 장점을 살리기 힘들 것 같은데 어떻게 생각하시나요? |
||
} | ||
|
||
public void execute(Input input, Print printer) { | ||
|
||
while (true) { | ||
String selection = input.getInput(); | ||
String selection = inputMenuSelection(input); | ||
CalculatorStrategy selectedStrategy = getStrategyOrThrow(selection); | ||
performStrategy(input, printer, selectedStrategy); | ||
} | ||
} | ||
|
||
private void performStrategy(Input input, Print printer, CalculatorStrategy selectedStrategy) { | ||
selectedStrategy.execute(input, printer, store); | ||
} | ||
|
||
private CalculatorStrategy getStrategyOrThrow(String selection) { | ||
CalculatorStrategy selectedStrategy = strategies.get(selection); | ||
if (selectedStrategy == null) { | ||
throw new IllegalArgumentException(CalculatorMessage.INVALID_INPUT.message); | ||
} | ||
return selectedStrategy; | ||
} | ||
Comment on lines
+44
to
+50
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. OrThrow라는 이름은 특정 상황이 만족하면 예외를 던질 수도 있다고 느껴져요. |
||
|
||
private String inputMenuSelection(Input input) { | ||
return input.getInput(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.wonu606.calculator.calculator; | ||
|
||
import java.util.List; | ||
|
||
public interface Calculator { | ||
|
||
double calculate(List<String> expression); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package com.wonu606.calculator.calculator; | ||
|
||
import com.wonu606.calculator.model.Operator; | ||
import java.util.ArrayDeque; | ||
import java.util.Deque; | ||
import java.util.List; | ||
import java.util.Objects; | ||
|
||
public class PostfixCalculator implements Calculator { | ||
|
||
@Override | ||
public double calculate(List<String> postfixExpression) { | ||
Deque<Double> operandStack = new ArrayDeque<>(); | ||
|
||
for (String token : postfixExpression) { | ||
handleToken(operandStack, token); | ||
} | ||
|
||
return operandStack.pop(); | ||
} | ||
|
||
private void handleToken(Deque<Double> operandStack, String token) { | ||
if (isOperator(token)) { | ||
Operator operator = Objects.requireNonNull(Operator.get(token)); | ||
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. Objects.requireNonNull()로 검사하기 보다는, Operator에 책임을 두는편이 더 좋아보여요 |
||
performOperation(operandStack, operator); | ||
return; | ||
} | ||
operandStack.push(Double.valueOf(token)); | ||
} | ||
|
||
private void performOperation(Deque<Double> operandStack, Operator operator) { | ||
double secondOperand = operandStack.pop(); | ||
double firstOperand = operandStack.pop(); | ||
Comment on lines
+32
to
+33
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. stack이 비어있다면 어떻게 되나요? |
||
|
||
double result = operator.apply(firstOperand, secondOperand); | ||
operandStack.push(result); | ||
} | ||
|
||
private boolean isOperator(String token) { | ||
return Operator.get(token) != null; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.wonu606.calculator.converter; | ||
|
||
public interface Converter<T, R> { | ||
|
||
R convert(T input); | ||
} |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,56 @@ | ||||||||
package com.wonu606.calculator.converter; | ||||||||
|
||||||||
import com.wonu606.calculator.model.Operator; | ||||||||
import java.util.ArrayDeque; | ||||||||
import java.util.ArrayList; | ||||||||
import java.util.Deque; | ||||||||
import java.util.List; | ||||||||
import java.util.Objects; | ||||||||
|
||||||||
public class InfixToPostfixConverter implements Converter<String, List<String>> { | ||||||||
|
||||||||
@Override | ||||||||
public List<String> convert(String infixExpression) { | ||||||||
Deque<String> operatorStack = new ArrayDeque<>(); | ||||||||
List<String> postfixExpression = new ArrayList<>(); | ||||||||
|
||||||||
String[] tokens = infixExpression.split("\\s"); | ||||||||
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. 만약 입력 요구사항이 달라지면 split은 어떻게 되나요? |
||||||||
|
||||||||
for (String token : tokens) { | ||||||||
processTokenIntoPostfix(operatorStack, postfixExpression, token); | ||||||||
} | ||||||||
|
||||||||
while (!operatorStack.isEmpty()) { | ||||||||
postfixExpression.add(operatorStack.pop()); | ||||||||
} | ||||||||
return postfixExpression; | ||||||||
} | ||||||||
|
||||||||
private void processTokenIntoPostfix( | ||||||||
Deque<String> operatorStack, List<String> postfixExpression, String token) { | ||||||||
if (isOperator(token)) { | ||||||||
popWhileHigherPrecedence(operatorStack, postfixExpression, token); | ||||||||
operatorStack.push(token); | ||||||||
return; | ||||||||
} | ||||||||
postfixExpression.add(token); | ||||||||
} | ||||||||
|
||||||||
private void popWhileHigherPrecedence( | ||||||||
Deque<String> operatorStack, List<String> postfixExpression, String tokenOperator) { | ||||||||
while (!operatorStack.isEmpty() | ||||||||
&& isHigherOrEqualPrecedence(operatorStack.peek(), tokenOperator)) { | ||||||||
Comment on lines
+41
to
+42
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. processTokenIntoPostfix부터 반복문이 굉장히 많은데, 성능적인 이슈는 없을까요? |
||||||||
postfixExpression.add(operatorStack.pop()); | ||||||||
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.
Suggested change
되도록 가독성을 위해 분리해주세요 |
||||||||
} | ||||||||
} | ||||||||
|
||||||||
private boolean isHigherOrEqualPrecedence(String peekOperator, String tokenOperator) { | ||||||||
int peekPrecedence = Objects.requireNonNull(Operator.get(peekOperator)).precedence; | ||||||||
int tokenPrecedence = Objects.requireNonNull(Operator.get(tokenOperator)).precedence; | ||||||||
return peekPrecedence <= tokenPrecedence; | ||||||||
} | ||||||||
|
||||||||
private boolean isOperator(String token) { | ||||||||
return Operator.get(token) != null; | ||||||||
} | ||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package com.wonu606.calculator.model; | ||
|
||
import java.util.Objects; | ||
|
||
public class CalculationResult { | ||
|
||
private final String expression; | ||
private final double result; | ||
|
||
public CalculationResult(String expression, double result) { | ||
this.expression = expression; | ||
this.result = result; | ||
} | ||
|
||
public String getExpression() { | ||
return expression; | ||
} | ||
|
||
public double getResult() { | ||
return result; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) { | ||
return true; | ||
} | ||
if (o == null || getClass() != o.getClass()) { | ||
return false; | ||
} | ||
CalculationResult that = (CalculationResult) o; | ||
return Double.compare(that.getResult(), getResult()) == 0 && Objects.equals( | ||
getExpression(), that.getExpression()); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(getExpression(), getResult()); | ||
} | ||
Comment on lines
+23
to
+39
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. equals와 hashCode를 재구현하신 이유가 뭘까요? 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. 재구현 이유
사용저장소에 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. 기본 equals와 hashCode 는 어떻게 구현되어있을까요? 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. Object.equals
Object.hashCode해시코드가 저는 자동 완성으로 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. 저장소에 CalculationResult이 제대로 저장되었는지 확인할 때 사용했다는 말은, 테스트 코드를 위해 비즈니스 로직을 변경한 것으로 보이는데 이게 올바른 방법일까요? 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. 일반적으로 테스트 코드를 위해 비즈니스 로직을 변경한 것이 문제가 된다고 생각합니다. 궁금한 점이 있습니다!! 인창 멘토님 |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package com.wonu606.calculator.model; | ||
|
||
import com.wonu606.calculator.util.CalculatorMessage; | ||
|
||
public enum Operator { | ||
ADD("+", 2) { | ||
@Override | ||
public double apply(double a, double b) { | ||
return a + b; | ||
} | ||
}, | ||
SUBTRACT("-", 2) { | ||
@Override | ||
public double apply(double a, double b) { | ||
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. a와 b가 같은 타입이기 때문에 순서에 실수가 있다면, 전혀 다른 결과값을 받을 수도 있겠네요 |
||
return a - b; | ||
} | ||
}, | ||
MULTIPLY("*", 1) { | ||
@Override | ||
public double apply(double a, double b) { | ||
return a * b; | ||
} | ||
}, | ||
DIVIDE("/", 1) { | ||
@Override | ||
public double apply(double a, double b) { | ||
if (b == 0) { | ||
throw new ArithmeticException(CalculatorMessage.NOT_DIVISIBLE_BY_ZERO.message); | ||
} | ||
return a / b; | ||
} | ||
}; | ||
|
||
public final String symbol; | ||
public final int precedence; | ||
|
||
Operator(String symbol, int precedence) { | ||
this.symbol = symbol; | ||
this.precedence = precedence; | ||
} | ||
|
||
public static Operator get(String symbol) { | ||
for (Operator operator : values()) { | ||
if (operator.symbol.equals(symbol)) { | ||
return operator; | ||
} | ||
} | ||
Comment on lines
+43
to
+47
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. Stream을 활용해보는건 어떨까요? |
||
return null; | ||
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. null을 반환하는 이유가 있을까요? |
||
} | ||
|
||
public abstract double apply(double a, double b); | ||
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. 추상 메소드를 사용한 이유가 있을까요?? |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.wonu606.calculator.storage; | ||
|
||
import com.wonu606.calculator.model.CalculationResult; | ||
import java.util.List; | ||
|
||
public interface Persistence { | ||
|
||
void saveResult(CalculationResult calculationResult); | ||
|
||
CalculationResult findResult(int sequence); | ||
|
||
List<CalculationResult> findAllResult(); | ||
|
||
void clear(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package com.wonu606.calculator.storage; | ||
|
||
import com.wonu606.calculator.model.CalculationResult; | ||
import com.wonu606.calculator.util.CalculatorMessage; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
public class ResultStore implements Persistence { | ||
|
||
private final List<CalculationResult> store = new ArrayList<>(); | ||
|
||
@Override | ||
public void saveResult(CalculationResult calculationResult) { | ||
store.add(calculationResult); | ||
} | ||
|
||
@Override | ||
public CalculationResult findResult(int sequence) { | ||
try { | ||
return store.get(sequence - 1); | ||
} catch (IndexOutOfBoundsException exception) { | ||
throw new IllegalArgumentException(CalculatorMessage.INVALID_ORDER.message); | ||
} | ||
} | ||
Comment on lines
+18
to
+24
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. sequence를 입력받아 하나의 결과 값을 찾을 수 있군요? |
||
|
||
@Override | ||
public List<CalculationResult> findAllResult() { | ||
return new ArrayList<>(store); | ||
} | ||
Comment on lines
+26
to
+29
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. 새로운 인스턴스에 반환하는 이유가 있을까요? 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. 그대로 반환해주게 된다면 외부에서 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. List를 일급 컬렉션으로 만들어 반환하는 것도 고려해보시죠 |
||
|
||
@Override | ||
public void clear() { | ||
store.clear(); | ||
} | ||
} |
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으로 CalculatorStrategy를 관리하는 것의 장점이 무엇인가요?
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.put()
만으로 추가하는 장점이 있다고 생각합니다!