객체지향 개념을 제대로 이해하기 위해 간단한 사칙연산을 할 수 있는 계산기를 만들어보는 과정입니다.
먼저 CaluclatorTest 를 생성합니다.
java/com/example/calculator/CalculatorTest.java
package com.example.calculator;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.params.provider.Arguments.arguments;
/**
* 요구사항
* 간단한 사칙연산을 할 수 있다.
* 양수로만 계산할 수 있다.
* 나눗셈에서 0을 나누는 경우 IllegalArgument 예외를 발생시킨다.
* MVC패턴 기반으로 구현한다.
*/
public class CalculatorTest {
// 1 + 2 ----> Calculator
// 3 <----
@DisplayName("덧셈 연산을 수행한다.")
@Test
void additionTest() {
int result = Calculator.calculate(1, "+", 2);
assertThat(result).isEqualTo(3);
}
@DisplayName("뺄셈 연산을 수행한다.")
@Test
void subtractionTest() {
int result = Calculator.calculate(1, "-", 2);
assertThat(result).isEqualTo(-1);
}
@DisplayName("연산을 수행한다.")
@ParameterizedTest
@MethodSource("formulaAndResult")
void parameterizedTestAnnotationWithTest(int operand1, String operator, int operand2, int result) {
int calculateResult = Calculator.calculate(operand1, operator, operand2);
assertThat(calculateResult).isEqualTo(result);
}
private static Stream<Arguments> formulaAndResult() {
return Stream.of(
arguments(1, "+", 2, 3),
arguments(1, "-", 2, -1),
arguments(1, "*", 2, 2),
arguments(4, "/", 2, 2)
);
}
}
- 주석에 1+2 ----> calculator
는 연산인자를 Calculator 객체로 위임하는 것을 뜻하고, 객체는 결과값을 반환해주는것을 의미합니다.
Calculator 는 계산기 객체입니다.
java/com/example/calculator/Calculator.java
package com.example.calculator;
public class Calculator {
public static int calculate(int operand1, String operator, int operand2) {
if ("+".equals(operator)) {
return operand1 + operand2;
}
else if ("-".equals(operator)) {
return operand1 - operand2;
}
else if ("*".equals(operator)) {
return operand1 * operand2;
}
else if ("/".equals(operator)) {
return operand1 / operand2;
}
return 0;
}
}
- 계산기는 parameter operator 가 "+"면 덧셈을, "-"면 뺄셈을, "*"는 곱셈, "/"는 나눗셈을 해서 반환합니다.
위에서는 간단하게 테스트를 생성해서 성공시키는 부분이었습니다.
이제 enum을 추가해보고, 작업을 위임 시키는 방식으로 리팩토링을 진행합니다.
java/com/example/calculator/ArithmeticOperator.java
package com.example.calculator;
import java.util.Arrays;
public enum ArithmeticOperator {
ADDITION("+") {
@Override
public int arithmeticCalculate(int operand1, int operand2) {
return operand1 + operand2;
}
},
SUBTRACTION("-") {
@Override
public int arithmeticCalculate(int operand1, int operand2) {
return operand1 - operand2;
}
},
MULTIPLICATION("*") {
@Override
public int arithmeticCalculate(int operand1, int operand2) {
return operand1 * operand2;
}
},
DIVISION("/") {
@Override
public int arithmeticCalculate(int operand1, int operand2) {
return operand1 / operand2;
}
};
private final String operator;
ArithmeticOperator(String operator) {
this.operator = operator;
}
// 추상 메서드
public abstract int arithmeticCalculate(final int operand1, final int operand2);
public static int calculate(int operand1, String operator, int operand2) {
ArithmeticOperator arithmeticOperator = Arrays.stream(values())
.filter(v -> v.operator.equals(operator))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("올바른 사칙연산이 아닙니다."));
return arithmeticOperator.arithmeticCalculate(operand1, operand2);
}
}
- enum 에는 추상메서드 arithmeticCalculate 를 생성합니다.
- 덧셈일 경우에는 인자들을 더해서 반환하는 방식으로 뺄셈,곱셈,나눗셈으로 추상메서드를 구현합니다.
java/com/example/calculator/Calculator.java
package com.example.calculator;
public class Calculator {
// enum 에게 다시 한번 작업을 위임
// 객체들끼리 협력해서 작업(CalculatorTest - Calculator - ArithmeticOperator)
public static int calculate(int operand1, String operator, int operand2) {
return ArithmeticOperator.calculate(operand1, operator, operand2);
}
}
- 기존에 operator 인자에 따라서 직접 하던 계산을 ArithmeticOperator 에게 위임합니다.
------> 객체들끼리 협력해서 작업이 이루어집니다. (CalculatorTest - Calculator - ArithmeticOperator)
다음은 객체지향에 맞게 다시 리팩토링입니다.
java/com/example/calculator/PositiveNumber.java
package com.example.calculator;
public class PositiveNumber {
private static final String ZERO_OR_NEGATIVE_NUMBER_EXCEPTION_MESSAGE = "0또는 음수를 전달할 수 없습니다.";
private final int value;
public PositiveNumber(int value) {
validate(value);
this.value = value;
}
private void validate(int value) {
if (isNegativeNumber(value)) {
throw new IllegalArgumentException(ZERO_OR_NEGATIVE_NUMBER_EXCEPTION_MESSAGE);
}
}
private boolean isNegativeNumber(int number) {
return number <= 0;
}
public int toInt() {
return value;
}
}
- 요구사항에서 계산기는 양수만을 계산할 수 있다고 했기때문에 음수일때는 IllegalArgumentException 을 발생시킵니다.
java/com/example/calculator/NewArithmeticOperator.java
package com.example.calculator;
public interface NewArithmeticOperator {
boolean supports(String operator);
int calculate(final PositiveNumber operand1, final PositiveNumber operand2);
}
- 인터페이스를 생성합니다.
- supports 는 파라미터로 들어오는 operator 를 지원하는지 여부를 확인하는 메서드입니다.
- calculate는 파라미터 1,2를 계산하는 메서드입니다.
java/com/example/calculator/AdditionOperator.java
package com.example.calculator;
public class AdditionOperator implements NewArithmeticOperator{
@Override
public boolean supports(String operator) {
return "+".equals(operator);
}
@Override
public int calculate(PositiveNumber operand1, PositiveNumber operand2) {
return operand1.toInt() + operand2.toInt();
}
}
- supports 메서드를 구현해서 파라미터 operator가 "+" 이면 true 이고 계산을 지원합니다.
- calculate 메서드 구현내용은 양수인 파라미터 1,2 를 받아서 덧셈 후 반환합니다.
- 뺄셈, 곱셈, 나눗셈 모두 같은 방식입니다.
package com.example.calculator;
public class SubtractionOperator implements NewArithmeticOperator{
@Override
public boolean supports(String operator) {
return "-".equals(operator);
}
@Override
public int calculate(PositiveNumber operand1, PositiveNumber operand2) {
return operand1.toInt() - operand2.toInt();
}
}
package com.example.calculator;
public class MultiplicationOperator implements NewArithmeticOperator{
@Override
public boolean supports(String operator) {
return "*".equals(operator);
}
@Override
public int calculate(PositiveNumber operand1, PositiveNumber operand2) {
return operand1.toInt() * operand2.toInt();
}
}
package com.example.calculator;
public class DivisionOperator implements NewArithmeticOperator{
@Override
public boolean supports(String operator) {
return "/".equals(operator);
}
@Override
public int calculate(PositiveNumber operand1, PositiveNumber operand2) {
return operand1.toInt() / operand2.toInt();
}
}
java/com/example/calculator/Calculator.java
package com.example.calculator;
import java.util.List;
public class Calculator {
private static final List<NewArithmeticOperator> arithmeticOperators = List.of(
new AdditionOperator(),
new DivisionOperator(),
new SubtractionOperator(),
new MultiplicationOperator()
);
// enum 에게 다시 한번 작업을 위임
// 객체들끼리 협력해서 작업(CalculatorTest - Calculator - ArithmeticOperator)
public static int calculate(PositiveNumber num1, String operator, PositiveNumber num2) {
return arithmeticOperators.stream()
.filter(arithmeticOperator -> arithmeticOperator.supports(operator))
.map(arithmeticOperator -> arithmeticOperator.calculate(num1, num2))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("올바른 사칙연산이 아닙니다."));
}
}
- 계산기 객체는 arithmeticOperators 에 List로 계산기들을 추가합니다.
- List<NewArithmeticOperator> arithmeticOperators 를 스트림으로 돌려서 작업을 위임합니다.
- Stream.filter() 는 Stream의 요소들을 필터링합니다. -> supports() 가 true인 요소를 필터링합니다.
- Stream.map() 은 요소를 다른 형태로 변경합니다. -> 파라미터 1,2를 필터링된 요소 Operator로 위임해서 계산합니다.
이제 마지막으로 테스트를 다시 실행시켜봅니다.
기존 CalculatorTest 에서 바뀐 부분은 계산할 파라미터값들을 양수로 감싼 부분입니다.
java/com/example/calculator/CalculatorTest.java
package com.example.calculator;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.params.provider.Arguments.arguments;
/**
* 요구사항
* 간단한 사칙연산을 할 수 있다.
* 양수로만 계산할 수 있다.
* 나눗셈에서 0을 나누는 경우 IllegalArgument 예외를 발생시킨다.
* MVC패턴 기반으로 구현한다.
*/
public class CalculatorTest {
// 1 + 2 ----> Calculator (Calculator 에게 작업을 위임)
// 3 <----
@DisplayName("덧셈 연산을 수행한다.")
@Test
void additionTest() {
int result = Calculator.calculate(new PositiveNumber(1), "+", new PositiveNumber(2));
assertThat(result).isEqualTo(3);
}
@DisplayName("뺄셈 연산을 수행한다.")
@Test
void subtractionTest() {
int result = Calculator.calculate(new PositiveNumber(1), "-", new PositiveNumber(2));
assertThat(result).isEqualTo(-1);
}
@DisplayName("연산을 수행한다.")
@ParameterizedTest
@MethodSource("formulaAndResult")
void parameterizedTestAnnotationWithTest(PositiveNumber operand1, String operator, PositiveNumber operand2, int result) {
int calculateResult = Calculator.calculate(operand1, operator, operand2);
assertThat(calculateResult).isEqualTo(result);
}
private static Stream<Arguments> formulaAndResult() {
return Stream.of(
arguments(new PositiveNumber(1), "+", new PositiveNumber(2), 3),
arguments(new PositiveNumber(1), "-", new PositiveNumber(2), -1),
arguments(new PositiveNumber(1), "*", new PositiveNumber(2), 2),
arguments(new PositiveNumber(4), "/", new PositiveNumber(2), 2)
);
}
}
테스트가 정상적으로 성공했습니다.
'공부내용 정리 > 자바' 카테고리의 다른 글
의존성 주입 (Dependency Injection, DI) (1) | 2023.10.17 |
---|---|
JDBC 프로그래밍 (0) | 2023.10.11 |
객체지향 패러다임 3 (학점계산기) (0) | 2023.09.05 |
객체지향 패러다임 1 (Password Validator) (0) | 2023.09.03 |
접근 제어자 (1) | 2021.12.05 |