요구사항
- 간단한 사칙연산을 할 수 있다.
- 양수로만 계산 할 수 있다.
- 나눗셈에서 0을 나누는 경우 적절한 예외(IllegalArgument, ZeroDivisionError)를 발생 시킬 수 있다.
절차적 코드
- Enum 기반:
ArithmeticOperator
Enum 클래스를 사용하여 구현합니다. 각 연산을 열거형 값으로 나타내며, 각 값은 연산을 수행하는calculate
메소드를 구현합니다.
자바
package org.example;
import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.Arguments;
import static org.assertj.core.api.Assertions.assertThat;
/**
* 요구사항
* 간단한 사칙연산을 할 수 있다.
* 양수로만 계산할 수 있다.
* 나눗셈에서 0을 나누는 경우 IllegalArgument 예외를 발생시킨다.
* MVC패턴(Model-View-Controller) base로 구현한다.
*/
public class CalculatorTest {
@DisplayName("덧셈 연산을 수행한다.")
@ParameterizedTest
@MethodSource("formulaAndResult")
void additionTest(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.arguments(1, "+", 2, 3),
Arguments.arguments(1, "-", 2, -1),
Arguments.arguments(1, "*", 2, 2),
Arguments.arguments(4, "/", 2, 2)
);
}
}
package org.example;
public class Calculator {
public static int calculate(int operand1, String operator, int operand2) {
switch (operator) {
case "+":
return add(operand1, operand2);
case "-":
return subtract(operand1, operand2);
case "*":
return multiply(operand1, operand2);
case "/":
return divide(operand1, operand2);
default:
throw new IllegalArgumentException("Invalid operator: " + operator);
}
}
private static int add(int operand1, int operand2) {
return operand1 + operand2;
}
private static int subtract(int operand1, int operand2) {
return operand1 - operand2;
}
private static int multiply(int operand1, int operand2) {
return operand1 * operand2;
}
private static int divide(int operand1, int operand2) {
if (operand2 == 0) {
throw new ArithmeticException("Cannot divide by zero.");
}
return operand1 / operand2;
}
}
파이썬
def add(operand1, operand2):
return operand1 + operand2
def subtract(operand1, operand2):
return operand1 - operand2
def multiply(operand1, operand2):
return operand1 * operand2
def divide(operand1, operand2):
if operand2 == 0:
raise ValueError("Cannot divide by zero.")
return operand1 / operand2
def validate_positive_number(value):
if value < 0:
raise ValueError("Negative numbers are not allowed.")
return value
def calculate(num1, operator, num2):
operand1 = validate_positive_number(num1)
operand2 = validate_positive_number(num2)
if operator == "+":
return add(operand1, operand2)
elif operator == "-":
return subtract(operand1, operand2)
elif operator == "*":
return multiply(operand1, operand2)
elif operator == "/":
return divide(operand1, operand2)
else:
raise ValueError("Invalid operator.")
if __name__ == '__main__':
num1 = 10
num2 = 4
# 사칙연산 수행
print(calculate(num1, "+", num2)) # 출력: 14
print(calculate(num1, "-", num2)) # 출력: 6
print(calculate(num1, "*", num2)) # 출력: 40
print(calculate(num1, "/", num2)) # 출력: 2.5
객체지향 코드1
자바
package org.example.calculator.domain;
import java.util.Arrays;
public enum ArithmeticOperator {
ADDITION("+") {
@Override
public int calculate(final int operand1, final int operand2) {
return operand1 + operand2;
}
},
SUBTRACTION("-") {
@Override
public int calculate(final int operand1, final int operand2) {
return operand1 - operand2;
}
},
MULTIPLICATION("*") {
@Override
public int calculate(final int operand1, final int operand2) {
return operand1 * operand2;
}
}, DIVISION("/") {
@Override
public int calculate(final int operand1, final int operand2) {
if (operand2 == 0) {
throw new IllegalArgumentException("0으로 나눌 수 없습니다.");
}
return operand1 / operand2;
}
};
private final String operator;
ArithmeticOperator(String operator) {
this.operator = operator;
}
public abstract int calculate(final int operand1, final int operand2);
public static int calculate(final int operand1, final String operator, final int operand2) {
ArithmeticOperator selectedArithmeticOperator = Arrays.stream(ArithmeticOperator.values())
.filter(v -> v.operator.equals(operator))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("올바른 사칙연산이 아닙니다."));
return selectedArithmeticOperator.calculate(operand1, operand2);
}
}
package org.example.calculator.domain;
public class PositiveNumber {
private static final String NEGATIVE_NUMBER_EXCEPTION_MESSAGE = "음수를 전달할 수 없습니다.";
private final int value;
public PositiveNumber(int value) {
validate(value);
this.value = value;
}
private void validate(int value) {
if (isNegativeNumber(value)) {
throw new IllegalArgumentException(NEGATIVE_NUMBER_EXCEPTION_MESSAGE);
}
}
private boolean isNegativeNumber(int number) {
return number < 0;
}
public int toInt() {
return value;
}
}
package org.example.calculator.domain;
public class Calculator {
public static int calculate(PositiveNumber num1, String operator, PositiveNumber num2) {
return ArithmeticOperator.calculate(num1.toInt(), operator, num2.toInt());
}
}
파이썬
calculator1패키지를 만듭니다.
__init__.py
모듈을 작성합니다.
# calculator1/__init__.py
Number = int | float
basic_arithmetic모듈을 작성합니다.
# calculator1/basic_arithmetic.py
from operation_strategy import OperationStrategy
from calculator1 import Number
class BasicArithmeticOperation(OperationStrategy):
@staticmethod
def add(operand1: Number, operand2: Number) -> Number:
return operand1 + operand2
@staticmethod
def subtract(operand1: Number, operand2: Number) -> Number:
return operand1 - operand2
@staticmethod
def multiply(operand1: Number, operand2: Number) -> Number:
return operand1 * operand2
@staticmethod
def divide(operand1: Number, operand2: Number) -> Number:
return operand1 / operand2
operation_strategy 모듈을 작성합니다.
# calculator1/operation_strategy.py
from abc import ABC, abstractmethod
from calculator1 import Number
class OperationStrategy(ABC):
@staticmethod
@abstractmethod
def add(operand1: Number, operand2: Number) -> Number:
pass
@staticmethod
@abstractmethod
def subtract(operand1: Number, operand2: Number) -> Number:
pass
@staticmethod
@abstractmethod
def multiply(operand1: Number, operand2: Number) -> Number:
pass
@staticmethod
@abstractmethod
def divide(operand1: Number, operand2: Number) -> Number:
pass
@classmethod
def execute_operation(cls, operator: str, operand1: Number, operand2: Number) -> Number:
operations = {
"+": cls.add,
"-": cls.subtract,
"*": cls.multiply,
"/": cls.divide,
}
if operator in operations:
return operations[operator](operand1, operand2)
else:
raise ValueError("Invalid operator")
PositiveNumber
모듈을 작성합니다.
# calculator1/positive_number.py
from calculator1 import Number
class PositiveNumber:
def __init__(self, value: Number):
self.__validate(value)
self.value = value
@staticmethod
def __validate(value: Number) -> None:
if value < 0:
raise ValueError("Negative numbers are not allowed.")
def to_int(self) -> Number:
return self.value
calculator모듈을 작성합니다.
# calculator1/calculator.py
from positive_number import PositiveNumber
from basic_arithmetic_operation import BasicArithmeticOperation
class Calculator:
@staticmethod
def calculate(num1: PositiveNumber, operator: str, num2: PositiveNumber) -> int:
operand1 = num1.to_int()
operand2 = num2.to_int()
result: int = BasicArithmeticOperation.execute_operation(operator, operand1, operand2)
return result
if __name__ == '__main__':
num1 = PositiveNumber(10)
num2 = PositiveNumber(4)
# 사칙연산 수행
print(Calculator.calculate(num1, "+", num2)) # 15
print(Calculator.calculate(num1, "-", num2)) # 5
print(Calculator.calculate(num1, "*", num2)) # 50
print(Calculator.calculate(num1, "/", num2)) # 2.0
객체지향 코드2
계산기 주요 기능을 제공하는 클래스입니다.
이 클래스는 사칙 연산을 수행하는 정적 메서드 calculate를 포함하고 있습니다.
package org.example;
import org.example.calculator.*;
import java.util.List;
public class Calculator {
private static final List<NewArithmeticOperator> arithmeticOperators = List.of(
new AdditionOperator(),
new SubtractOperator(),
new MultiplyOperator(),
new DivideOperator()
);
public static int calculate(int operand1, String operator, int operand2){
return arithmeticOperators.stream()
.filter(arithmeticOperator -> arithmeticOperator.support(operator))
.map(arithmeticOperator -> arithmeticOperator.calculate(operand1, operand2))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("올바른 사칙연산이 아닙니다"));
}
}
사칙연산을 테스트하는 클래스입니다.
이 클래스는 JUnit을 사용하여 Calculator 클래스의 calculate 메서드를 테스트합니다.
테스트는 다양한 사칙연산에 대해 수행됩니다.
package org.example;
import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.Arguments;
import static org.assertj.core.api.Assertions.assertThat;
/**
* 요구사항
* 간단한 사칙연산을 할 수 있다.
* 양수로만 계산할 수 있다.
* 나눗셈에서 0을 나누는 경우 IllegalArgument 예외를 발생시킨다.
* MVC패턴(Model-View-Controller) base로 구현한다.
*/
public class CalculatorTest {
@DisplayName("사칙연산 테스트를 수행한다.")
@ParameterizedTest
@MethodSource("formulaAndResult")
void calculateTest(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.arguments(1, "+", 2, 3),
Arguments.arguments(1, "-", 2, -1),
Arguments.arguments(1, "*", 2, 2),
Arguments.arguments(4, "/", 2, 2)
);
}
}
양수를 나타내는 클래스입니다.
이 클래스는 생성 시점에 전달된 값이 양수인지 검증합니다.
양수가 아닐 경우 IllegalArgumentException을 발생시킵니다.
package org.example.calculator.domain;
public class PositiveNumber {
public 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;
}
}
사칙 연산을 지원하는 연산자 인터페이스입니다.
support 메서드를 통해 해당 연산자가 지원하는지 확인할 수 있습니다.
calculate 메서드는 두 피연산자에 대한 계산을 수행합니다.
package org.example.calculator;
public interface NewArithmeticOperator {
boolean support(String operator);
int calculate(int operand1, int operand2);
}
덧셈 연산을 수행하는 클래스입니다.
NewArithmeticOperator 인터페이스를 구현합니다.
package org.example.calculator;
public class AdditionOperator implements NewArithmeticOperator{
@Override
public boolean support(String operator) {
return "+".equals(operator);
}
@Override
public int calculate(int operand1, int operand2) {
return operand1 + operand2;
}
}
뺄셈 연산을 수행하는 클래스입니다.
NewArithmeticOperator 인터페이스를 구현합니다.
package org.example.calculator;
public class SubtractOperator implements NewArithmeticOperator{
@Override
public boolean support(String operator) {
return "-".equals(operator);
}
@Override
public int calculate(int operand1, int operand2) {
return operand1 - operand2;
}
}
곱셈 연산을 수행하는 클래스입니다.
NewArithmeticOperator 인터페이스를 구현합니다.
package org.example.calculator;
public class MultiplyOperator implements NewArithmeticOperator{
@Override
public boolean support(String operator) {
return "*".equals(operator);
}
@Override
public int calculate(int operand1, int operand2) {
return operand1 * operand2;
}
}
나눗셈 연산을 수행하는 클래스입니다.
NewArithmeticOperator 인터페이스를 구현합니다.
0으로 나누는 경우를 처리하기 위해 추가적인 검증이 필요할 수 있습니다.
package org.example.calculator;
public class DivideOperator implements NewArithmeticOperator{
@Override
public boolean support(String operator) {
return "/".equals(operator);
}
@Override
public int calculate(int operand1, int operand2) {
return operand1 / operand2;
}
}
파이썬
from typing import Generic, TypeVar, List
from abc import ABC, abstractmethod
# 숫자 타입 변수 정의 (정수 또는 부동소수점)
NumberType = TypeVar('NumberType', int, float)
class NewArithmeticOperator(ABC, Generic[NumberType]):
@abstractmethod
def support(self, operator: str) -> bool:
pass
@abstractmethod
def calculate(self, operand1: NumberType, operand2: NumberType) -> NumberType:
pass
class AdditionOperator(NewArithmeticOperator[NumberType]):
def support(self, operator: str) -> bool:
return operator == "+"
def calculate(self, operand1: NumberType, operand2: NumberType) -> NumberType:
return operand1 + operand2
class SubtractOperator(NewArithmeticOperator[NumberType]):
def support(self, operator: str) -> bool:
return operator == "-"
def calculate(self, operand1: NumberType, operand2: NumberType) -> NumberType:
return operand1 - operand2
class MultiplyOperator(NewArithmeticOperator[NumberType]):
def support(self, operator: str) -> bool:
return operator == "*"
def calculate(self, operand1: NumberType, operand2: NumberType) -> NumberType:
return operand1 * operand2
class DivideOperator(NewArithmeticOperator[NumberType]):
def support(self, operator: str) -> bool:
return operator == "/"
def calculate(self, operand1: NumberType, operand2: NumberType) -> NumberType:
if operand2 == 0:
raise ValueError("Cannot divide by zero.")
return operand1 / operand2
class Calculator:
arithmetic_operators: List[NewArithmeticOperator[NumberType]] = [
AdditionOperator(),
SubtractOperator(),
MultiplyOperator(),
DivideOperator()
]
@staticmethod
def calculate(operand1: NumberType, operator: str, operand2: NumberType) -> NumberType:
for arithmetic_operator in Calculator.arithmetic_operators:
if arithmetic_operator.support(operator):
return arithmetic_operator.calculate(operand1, operand2)
raise ValueError("올바른 사칙연산이 아닙니다")
class PositiveNumber(Generic[NumberType]):
ZERO_OR_NEGATIVE_NUMBER_EXCEPTION_MESSAGE = "0 또는 음수를 전달할 수 없습니다."
def __init__(self, value: NumberType):
self.validate(value)
self.value: NumberType = value
def validate(self, value: NumberType) -> None:
if value <= 0:
raise ValueError(self.ZERO_OR_NEGATIVE_NUMBER_EXCEPTION_MESSAGE)
def to_number(self) -> NumberType:
return self.value
if __name__ == '__main__':
num1 = PositiveNumber(10).to_number()
num2 = PositiveNumber(4).to_number()
# 사칙연산 수행
print(Calculator.calculate(num1, "+", num2)) # 14
print(Calculator.calculate(num1, "-", num2)) # 6
print(Calculator.calculate(num1, "*", num2)) # 40
print(Calculator.calculate(num1, "/", num2)) # 2.5
코드 설명
위 파이썬 코드는 사칙 연산을 수행하는 계산기의 기능을 구현한 것으로, 여러 객체지향 프로그래밍(OOP)과 제네릭 프로그래밍의 특징을 보여줍니다. 주요 구성 요소는 다음과 같습니다:
NumberType 제네릭 타입 변수
NumberType
은 정수(int
) 또는 부동소수점(float
) 타입을 나타내는 타입 변수입니다. 이를 사용하여 다양한 숫자 타입을 처리할 수 있는 유연성을 제공합니다.
NewArithmeticOperator 추상 베이스 클래스
NewArithmeticOperator
는 사칙 연산을 나타내는 추상 베이스 클래스(ABC)로, 제네릭 타입 NumberType
을 사용합니다. 이 클래스는 두 가지 추상 메서드를 정의합니다:
support(self, operator: str) -> bool
: 주어진 연산자 문자열을 지원하는지 여부를 반환합니다.calculate(self, operand1: NumberType, operand2: NumberType) -> NumberType
: 두 피연산자에 대한 계산을 수행하고 결과를 반환합니다.
사칙 연산 클래스들
AdditionOperator
, SubtractOperator
, MultiplyOperator
, DivideOperator
클래스들은 NewArithmeticOperator
추상 클래스를 구현합니다. 각 클래스는 특정 사칙 연산을 나타내며, support
메서드를 통해 해당 연산을 처리할 수 있는지를 판단하고, calculate
메서드로 실제 연산을 수행합니다.
Calculator 클래스
Calculator
클래스는 사칙 연산을 수행하는 정적 메서드 calculate
를 포함합니다. 이 메서드는 피연산자와 연산자를 인자로 받아, 등록된 사칙 연산 클래스들 중 해당 연산을 처리할 수 있는 클래스를 찾아 연산을 수행합니다. 연산자를 처리할 수 있는 클래스가 없을 경우, ValueError
를 발생시킵니다.
PositiveNumber 클래스
PositiveNumber
클래스는 양수만을 허용하는 래퍼 클래스로, 제네릭 타입 NumberType
을 사용합니다. 생성자에서 주어진 값이 양수인지 검증하며, 그렇지 않을 경우 ValueError
를 발생시킵니다.
메인 실행 부
if __name__ == '__main__':
부분에서는 PositiveNumber
클래스를 사용하여 사칙 연산을 수행하는 예를 보여줍니다. PositiveNumber
인스턴스에서 to_number
메서드를 사용하여 실제 숫자 값을 얻고, 이를 Calculator.calculate
메서드에 전달하여 사칙 연산의 결과를 출력합니다.
이 코드는 OOP의 원칙을 따르며, 특히 다형성과 캡슐화를 활용하여 사칙 연산 기능을 유연하고 확장 가능한 방식으로 구현합니다. 또한, 제네릭을 사용하여 타입 안전성을 높이면서도 다양한 숫자 타입을 처리할 수 있는 유연성을 제공합니다.
'여러가지 > 이것저것' 카테고리의 다른 글
[OOP] 음식 주문하기 (0) | 2024.04.04 |
---|---|
OOP? (0) | 2024.04.03 |
AMQP? (0) | 2024.04.03 |
MQTT? (0) | 2024.04.03 |
커널(Kernel) (0) | 2024.03.22 |