1. 리팩토링 반영 List
1) 객체 지향 관점에서 최대한 클래스로 묶을 수 있는 부분은 묶자.
- 문자열 split Class(Formula Class), 사칙연산 Class(Operator Class), 연산 실행 Class(Calculator)
2) 다양한 Test 코드 만들기(예외 처리)
- 분리한 Class 별로 Test
- 인수가 null이거나 공백일 경우 (예 - "2 + + 3 * 4")
- 사칙연산 기호가 아닐 경우 (예 - "2 & 3 * 2")
- 0으로 나눌 경우 (예 - "3 / 0 + 2")
2. 3차 실습 코드(caclulator_v2)
package calculator_v3;
import java.util.regex.Pattern;
public class Calculator {
private Operator currentOperator;
private static final String regExp = "[0-9]+";
public int calculateFormula(String[] formulaArr){
//초기화
int result = 0;
currentOperator = Operator.PLUS;
for(String formulaPart : formulaArr){
int addValue = calculatePartial(result, formulaPart);
if(addValue != 0) {
result = addValue;
}
}
return result;
}
private int calculatePartial(int result, String formulaPart){
if(Pattern.matches(regExp, formulaPart)){
//숫자일 경우
return currentOperator.operate(result, Integer.parseInt(formulaPart));
}
//문자 설정
currentOperator = Operator.findSymbol(formulaPart);
return 0;
}
}
package calculator_v3;
public class Formula {
private String formula;
private String splitStr;
public Formula(String formula, String splitStr){
this.formula = formula;
this.splitStr = splitStr;
}
public String[] splitFormula(){
if(splitStr != null){
return formula.split(splitStr);
}
throw new IllegalArgumentException();
}
}
Enum Class) 상수 정의 및 초기화
각 상수(PLUS, MINUS, MULTIPLY, DIVIDE)는 enum 생성자를 호출하여 초기화된다. 상수 정의 부분에서 생성자에 전달되는 인수는 각각의 필드와 연관되어 있다.
예)
- PLUS("+", (num1, num2) -> num1 + num2):
- symbol 필드는 +로 초기화된다.
- operation 필드는 (num1, num2) -> num1 + num2라는 람다 표현식으로 초기화된다.
package calculator_v3;
import java.util.Collections;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public enum Operator {
PLUS("+", (num1, num2) -> num1 + num2),
MINUS("-", (num1, num2) -> num1 - num2),
MULTIPLY("*", (num1, num2) -> num1 * num2),
DIVIDE("/", (num1, num2) -> {
if(num2 == 0) throw new IllegalArgumentException();
return num1 / num2;
});
private String symbol;
private BiFunction<Integer, Integer, Integer> operation;
private static final Map<String, Operator> operatorMap
= Collections.unmodifiableMap(Stream.of(values()).collect(
Collectors.toMap(Operator::getSymbol, operator -> operator)));
Operator(String symbol, BiFunction<Integer, Integer, Integer> operation){
this.symbol = symbol;
this.operation = operation;
}
public String getSymbol(){
return symbol;
}
public int operate(int num1, int num2){
return operation.apply(num1, num2);
}
public static Operator findSymbol(String symbol){
Operator operator = operatorMap.get(symbol);
if(operator == null){
throw new IllegalArgumentException();
}
return operator;
}
}
package study;
import calculator_v3.Calculator;
import calculator_v3.Formula;
import calculator_v3.Operator;
import org.assertj.core.util.TriFunction;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.Assertions.*;
public class Calculator_v3 {
public Operator currentOperator;
public Calculator calculator = new Calculator();
@DisplayName("Formula Class 테스트")
@Test
void formulaTest(){
String[] formulaArr = new Formula("2 + 3 * 4 / 2", " ").splitFormula();
int result = calculator.calculateFormula(formulaArr);
assertThat(10).isEqualTo(result);
}
@DisplayName("Formula 공백 test - IllegalArgumentException")
@Test
void formulaTest2(){
assertThatThrownBy(() -> {
String[] formulaArr = new Formula("2 + * 4 / 2", " ").splitFormula();
int result = calculator.calculateFormula(formulaArr);
}).isInstanceOf(IllegalArgumentException.class).hasToString("공백 예외!!");
}
@DisplayName("Formula 사칙연산이 아닌 기호 test - IllegalArgumentException")
@Test
void formulaTest3(){
assertThatThrownBy(() -> {
String[] formulaArr = new Formula("2 & 4 * 4 / 2", " ").splitFormula();
int result = calculator.calculateFormula(formulaArr);
}).isInstanceOf(IllegalArgumentException.class).hasToString("사칙 연산 예외!!");
}
@DisplayName("Formula 0으로 나눌 때 test - IllegalArgumentException")
@Test
void formulaTest4(){
assertThatThrownBy(() -> {
String[] formulaArr = new Formula("2 / 0 * 4 / 2", " ").splitFormula();
int result = calculator.calculateFormula(formulaArr);
}).isInstanceOf(IllegalArgumentException.class).hasToString("0으로 나눌 때 예외!!");
}
@DisplayName("Operator Class 테스트")
@Test
void operatorTest1(){
assertThat(10).isEqualTo(Operator.PLUS.operate(5,5));
}
}
[느낀점]
1. 최대한 Class를 작게 나눠서 그 Class 객체의 책임과 역할에 맞게 코드를 작성하자.
처음 코드 작성했을 때는 한 Class에 많은 역할이 들어가 있었다. 더 작게 분리해서 리팩토링을 해보니 확실히 Class의 역할이 눈에 잘 보이고 기능을 찾기 수월했다.
2. Enum Class와 가까워 졌다.
여기서는 필드를 명시만 하고 끝내는 게 아니라 해당 필드에 관련된 메서드도 만들어서 사용하므로써 꽉 찬(?) 사칙연산 Class가 되었다. Enum에 관련된 메서드는 Enum Class에 명시해서 사용할 수 있도록 만들어보자.
[참고]
Enum 조회 성능 높여보기 - HashMap을 이용해서 빠르게 조회해보자
Enum 조회 성능 높여보기 자바의 Enum 에서 매핑을 할 때 find 메서드를 정의해서 알맞는 enum 을 찾는 로직을 많이 구현해본적이 있을 것 같아요. 다음과 같은 코드가 익숙하실텐테요. 저도 이런식으
pjh3749.tistory.com
2차 피드백
##### '객체지향' 관점을 견지하고 구현하는 것이 중요하다 ```{.no-highlight} 평소에 코드를 짤 때에 항상 클래스로 처리가 가능한 것이 있으면 되도록 클래스로 …
wikidocs.net
'Java > TDD' 카테고리의 다른 글
[TDD] 문자열 계산기 && 1차 리팩토링 (0) | 2024.05.28 |
---|