본문 바로가기

공부내용 정리/자바

객체지향 패러다임 2 (계산기)

객체지향 개념을 제대로 이해하기 위해 간단한 사칙연산을 할 수 있는 계산기를 만들어보는 과정입니다.

 

먼저 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)
        );
    }
}

 

테스트가 정상적으로 성공했습니다.