계산기에 대한 개인과제가 끝나는 날이여서 전체적으로 정리를 하려고한다.
Level1~3까지 있으며, 그 안에도 요구사항이 꽤나 많지만 하나하나 하다보면 이 포스트는 끝도없이 길어질것을 알기때문에 포인트들만 뽑아서 정리해보도록 하겠다.
level1의 학습목표는 다음과 같다.
💡 학습 목표
요구사항을 줄여서 얘기해보자면
이렇게 설명할 수 있겠다. 이에 대해 내가 짠 코드이다.
(배열같은 경우는 주석으로 바꾸어놓았다.)
import java.util.ArrayList;
import java.util.Scanner;
public class App {
public static void main(String[] args) {
ArrayList<String> resultList = new ArrayList<String>();
/*배열관련
String[] resultList = new String[10];
*/
int index = 0;
Scanner sc = new Scanner(System.in);
while (true) {
System.out.print("첫 번째 숫자를 입력하세요: ");
int firstNum = sc.nextInt();
System.out.print("두 번째 숫자를 입력하세요: ");
int secondNum = sc.nextInt();
System.out.print("사칙연산 기호를 입력하세요: ");
sc.nextLine();
String operator = sc.nextLine();
char real_operator = operator.charAt(0);
int result = 0;
switch (real_operator) {
case '+':
result = firstNum + secondNum;
break;
case '-':
result = firstNum - secondNum;
break;
case '*':
result = firstNum * secondNum;
break;
case '/':
result = firstNum / secondNum;
break;
}
System.out.println("결과: " + result);
resultList.add(firstNum + " " + real_operator + " " + secondNum + " = " + result);
/*배열관련
if(index <= 9) {
resultList[index] = firstNum + " " + real_operator + " " + secondNum + " = " + result;
}else{
for(int i=0; i<9; i++){
resultList[i] = resultList[i+1];
}
resultList[9] = firstNum + " " + real_operator + " " + secondNum + " = " + result;
}*/
System.out.println("가장 먼저 저장된 연산 결과를 삭제하시겠습니까? (remove 입력 시 삭제)");
String remove = sc.nextLine();
if (remove.equals("remove")) {
resultList.remove(0);
}
System.out.println("저장된 연산결과를 조회하시겠습니까? (inquiry 입력 시 조회)");
String inquiry = sc.nextLine();
if (inquiry.equals("inquiry")) {
System.out.println("결과값 ");
for (String s : resultList) {
System.out.println(s);
}
}
System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
String exit = sc.nextLine();
if (exit.equals("exit")) {
break;
}
/*배열관련
index++;
*/
}
}
}
처음에 값을 저장할 때는 int의 결과값으로만 만들었다가 이 연산의 대한 전체적인 흐름을 저장하면 좋을 것 같아 연산과정도 같이 넣어 String으로 만들었다.
또한 배열에 10개를 넣었을 때는 이후에 저장되는 값들은 가장 처음으로 들어간 값을 제외하고 앞으로 옮긴다음 마지막에 저장하라는 요구사항도 있어 위와같이 조건문 안에 for문을 넣어 앞으로 이동하고 마지막 index 9에 해당값을 넣었다.
level2부터는 조금씩 어려워진다.. 내가 생각하기에는 요구사항도 약간 모호해져서 파악하는데 시간이 걸렸던 것같다.
level 2의 학습목표는 다음과 같다.
💡 학습 목표
요구사항을 얘기해보자면
이렇게 설명할 수 있겠다. 보기만해도 생각보다 머리가 아픈 작업들이다..
여기에서 생각을 먼저해서 나만의 말로 다시한번 정리하자면 다음과 같다.
우선 분리하기전 calculator 코드를 작성해보겠다.
import java.util.ArrayList;
public class Calculator {
private ArrayList<String> resultList = new ArrayList<>();//사칙연산결과 저장 리스트
private ArrayList<String> circleResultList = new ArrayList<>(); //원의 넓이 결과 저장 리스트
static final double PI = 3.14159265358979323846; //pi값은 변하지않는 상수이기 때문에 static final을 사용한다.
public Calculator(){ }
public Calculator(ArrayList<String> resultList, ArrayList<String> circleResultList){
this.resultList = resultList;
this.circleResultList = circleResultList;
}
public int calculate(int firstNum, int secondNum, char operator) throws BadInputException{
//사칙연산
if (operator == '/' && secondNum == 0){
throw new BadInputException("나눗셈에서 분모에 0이 들어올 수 없습니다.");
}
if (!(operator == '+' || operator == '-' || operator == '*' || operator == '/')){
throw new BadInputException("사칙연산 기호를 정확히 입력해주세요");
}
int result = 0;
switch (operator) {
case '+':
result = firstNum + secondNum;
break;
case '-':
result = firstNum - secondNum;
break;
case '*':
result = firstNum * secondNum;
break;
case '/':
result = firstNum / secondNum;
break;
}
resultList.add(firstNum + " " + operator + " " + secondNum + " = " + result);
return result;
}
public void removeResult() { //사칙연산 결과 제일 처음거 삭제
if (!resultList.isEmpty()) {
resultList.remove(0);
}
}
public void inquiryResults() { //사칙연산결과 조회
if (!resultList.isEmpty()) {
System.out.println("[저장된 사칙연산결과 조회]");
for (String s : resultList) {
System.out.println(s);
}
}else{
System.out.println("저장된 사칙연산결과가 없습니다.");
}
}
public double calculateCircleArea(int radius) { //원의 넓이 구하는 메소드
double result = 0;
result = radius * radius * PI;
return result;
}
public void inquiryCircleResults() { //원의 넓이 연산결과 조회
if (!circleResultList.isEmpty()) {
System.out.println("[저장된 원의 넓이 연산결과 조회]");
for (String s : circleResultList) {
System.out.println(s);
}
}else{
System.out.println("저장된 원의 넓이 연산결과가 없습니다.");
}
}
public ArrayList<String> getResultList() {
return resultList;
}
public ArrayList<String> getCircleResultList() {
return circleResultList;
}
}
setter는 사용할 일이 없는 것 같아 만들지않았고 getter는 app에서 불러와야하므로 만들어두었다.
또한, 객체가 만들어질때마다 초기화되어야하니 생성자에는 사칙연산결과리스트와 원넓이 결과 리스트를 넣어주었고, static final를 하나 넣으라해서 pi값은 변하지않는 값이니 pi값으로 넣어두었다.
이것을 사칙연산별로, 원넓이 별로 클래스를 분리해보겠다.
//AddOperator, SubtractOperator, MultiplyOperator, DivideOperator 같으므로 add만 넣음)
import java.util.ArrayList;
public class AddOperator {
public double operate(int firstNum, int secondNum){
return firstNum + secondNum;
}
}
//calculator
import java.util.ArrayList;
public abstract class Calculator {
private ArrayList<String> resultList = new ArrayList<>();//사칙연산결과 저장 리스트
public static final double PI = 3.14159265358979323846; //pi값은 변하지않는 상수이기 때문에 static final을 사용한다.
public Calculator(ArrayList<String> resultList){
this.resultList = resultList;
}
public void removeResult() { //사칙연산 결과 제일 처음거 삭제
if (!resultList.isEmpty()) {
resultList.remove(0);
}
}
public ArrayList<String> getResultList() {
return resultList;
}
abstract void inquiryResult();
}
//arithmeticCalculator-사칙연산
import java.util.ArrayList;
public class ArithmeticCalculator extends Calculator {
private final AddOperator addOperator;
private final SubtractOperator subtractOperator;
private final MultiplyOperator multiplyOperator;
private final DivideOperator divideOperator;
private final ModOperator modOperator;
public ArithmeticCalculator(ArrayList<String> resultList, AddOperator addOperator, SubtractOperator subtractOperator,
MultiplyOperator multiplyOperator, DivideOperator divideOperator, ModOperator modOperator) {
super(resultList);
this.addOperator = addOperator;
this.subtractOperator = subtractOperator;
this.multiplyOperator = multiplyOperator;
this.divideOperator = divideOperator;
this.modOperator = modOperator;
}
public double calculate(int firstNum, int secondNum, char operator) throws BadInputException{
//사칙연산
if (operator == '/' && secondNum == 0){
throw new BadInputException("나눗셈에서 분모에 0이 들어올 수 없습니다.");
}
if (!(operator == '+' || operator == '-' || operator == '*' || operator == '/'|| operator == '%')){
throw new BadInputException("사칙연산 기호를 정확히 입력해주세요");
}
double result = 0;
switch (operator) {
case '+':
result = addOperator.operate(firstNum, secondNum);
break;
case '-':
result = subtractOperator.operate(firstNum,secondNum);
break;
case '*':
result = multiplyOperator.operate(firstNum,secondNum);
break;
case '/':
result = divideOperator.operate(firstNum,secondNum);
break;
case '%':
result = modOperator.operate(firstNum,secondNum);
break;
}
return result;
}
@Override
public void inquiryResult() { //사칙연산결과 조회
if (!super.getResultList().isEmpty()) {
System.out.println("[저장된 사칙연산결과 조회]");
for (String s : super.getResultList()) {
System.out.println(s);
}
}else{
System.out.println("저장된 사칙연산결과가 없습니다.");
}
}
}
//circleCalculator -원넓이
import java.util.ArrayList;
public class CircleCalculator extends Calculator {
public CircleCalculator(ArrayList<String> resultList) {
super(resultList);
}
public double calculate(int radius) {
return radius * radius * PI;
}
@Override
public void inquiryResult() { //원의 넓이 연산결과 조회
if (!super.getResultList().isEmpty()) {
System.out.println("[저장된 원의 넓이 연산결과 조회]");
for (String s : super.getResultList()) {
System.out.println(s);
}
}else{
System.out.println("저장된 원의 넓이 연산결과가 없습니다.");
}
}
}
여기에서 만약 mod를 추가한다고 하면 ModOperator 클래스도 만들고, arithmeticCalculator클래스의 가장 주요 기능인 calculate메서드도 수정하며, 생성자에 초기화를 해야하니 위에 private final ModOperator modOperator와 생성자에 해당 값을 추가하고... 엄청 일이 많아진다.
다른건 상관없다고 생각해도 가장 주요기능인 calculate 메서드를 계속 수정한다는 것 자체가 너무 큰 리스크이기 때문에 이부분에서는 add-mod까지 다 사용하는 operate를 가지는 인터페이스를 하나 만드는 것이 좋을 것 같다고 생각이 든다!
//operator 인터페이스
public interface Operator {
double operate(int num1, int num2) throws BadInputException;
}
//AddOperator 클래스
import java.util.ArrayList;
public class AddOperator implements Operator{
@Override
public double operate(int firstNum, int secondNum){
return firstNum + secondNum;
}
}
//ArithmeticCalculator 클래스
import java.util.ArrayList;
public class ArithmeticCalculator extends Calculator {
public ArithmeticCalculator(ArrayList<String> resultList) {
super(resultList);
}
public double calculate(int firstNum, int secondNum, char operator) throws BadInputException{
return operatorFactory(operator).operate(firstNum, secondNum);
}
private Operator operatorFactory(char operator) throws BadInputException {
return switch(operator){
case '+' -> new AddOperator();
case '-' -> new SubtractOperator();
case '*' -> new MultiplyOperator();
case '/' -> new DivideOperator();
case '%' -> new ModOperator();
default -> throw new BadInputException("사칙연산 기호를 정확히 입력해주세요.");
};
}
@Override
public void inquiryResult() { //사칙연산결과 조회
if (!super.getResultList().isEmpty()) {
System.out.println("[저장된 사칙연산결과 조회]");
for (String s : super.getResultList()) {
System.out.println(s);
}
}else{
System.out.println("저장된 사칙연산결과가 없습니다.");
}
}
}
람다식까지 사용하여 이렇게 만드니 훨~씬 간결하고 이뻐보인다.
여기에서 만약 기능을 더 추가하고 싶다면 그냥 operatorFactory에 아래 case하나만 더 추가하면된다.
여기부터는 심화과정이다. 제출도 목표가 level2였을 정도로..!
level3의 학습목표는 다음과 같다.
💡 학습 목표
여기서 Enum을 처음보는 것 같은데
Enum은 열거형이라고 불리며, 서로 연관된 상수들의 집합을 의미한다.
이를 사용하면 얻을수 있는 이점이 몇가지 있는데 다음과 같다.
이와 같다. 제네릭스와 람다 스트림은 이전 자바 강의관련 포스트에서 했으므로 넘어가겠다!(링크는 넣어둠..)
그래서 이 레벨3의 요구사항은 다음과 같다.
이거는 하나하나 코드를 써보아야겠다.(변경된 부분만!)
OperatorType이라는 enum 클래스를 만든다.
operator 값을 받아 해당 type을 반환하는 메서드를 추가한다.
//OperatorType enum 클래스
public enum OperatorType {
ADDITION('+'),
SUBTRACTION('-'),
MULTIPLICATION('*'),
DIVISION('/'),
MODULO('%');
private final char symbol;
OperatorType(char symbol) {
this.symbol = symbol;
}
/*
char operator 값을 받아 해당하는 OperatorType을 반환하는 정적 메서드 추가
*/
public static OperatorType fromOperator(char operator){
for(OperatorType type : OperatorType.values()){
if (type.symbol == operator){
return type;
}
}
throw new IllegalArgumentException("해당하는 연산자가 없습니다." + operator);
}
}
//ArithmeticCalculator 클래스
private Operator operatorFactory(char operator) throws BadInputException {
OperatorType operatorType = OperatorType.fromOperator(operator);
return switch(operatorType){
case ADDITION -> new AddOperator();
case SUBTRACTION -> new SubtractOperator();
case MULTIPLICATION -> new MultiplyOperator();
case DIVISION -> new DivideOperator();
case MODULO -> new ModOperator();
};
}
위와같이 operatortype을 선언한다음 fromoperator로 해당 type을 반환받은 값을 switch문에 작성하여 해당 enum들을 열거하여 사용했다.
우선 여기에서 int가 아닌 다른 타입 double, long등을 사용하기 위해선 int firstNumber, int secondNumber로 받던 부분들은 전부 다 수정을 해야한다..
꽤나 반복 작업이지만 예를 들어 add코드를 작성할 예정이다.
우선 ArithmeticCalculator에서 calculate부분도 operator를 제외하고는 T로 받을 것이며, 클래스 자체도 제네릭클래스라는 것을 표시해주어야한다.
그리고, operate를 통해 직접 연산을 하는 클래스들에서는 타입변환을 정확히 해주어야연산이 되기 때문에 타입을 지정해주는 클래스도 하나 만들겠다.
//ArithmeticCalculator클래스
import java.util.ArrayList;
public class ArithmeticCalculator<T extends Number> extends Calculator {
public final Class<T> type;
//클래스의 클래스타입을 선언한다..말이 좀 이상하지만 이부분은 따로 공부해야할것같다.
public ArithmeticCalculator(ArrayList<String> resultList, Class<T> type){
super(resultList);
this.type = type;
}
/*여기 이 생성자때문에 app에서도 사칙연산 인스턴스 만들때
ArithmeticCalculator<Integer> arithmeticCalculator = new ArithmeticCalculator(new ArrayList<>(), Integer.class);
이와같이 만들어야한다.
*/
public T calculate(T firstNum, T secondNum, char operator) throws BadInputException{
return operatorFactory(operator).operate(firstNum, secondNum);
}//제네릭 메소드
private Operator<T> operatorFactory(char operator) throws BadInputException {
OperatorType operatorType = OperatorType.fromOperator(operator);
return switch(operatorType){
case ADDITION -> new AddOperator(type);
case SUBTRACTION -> new SubtractOperator(type);
case MULTIPLICATION -> new MultiplyOperator(type);
case DIVISION -> new DivideOperator(type);
case MODULO -> new ModOperator(type);
};
}
@Override
public void inquiryResult() { //사칙연산결과 조회
if (!super.getResultList().isEmpty()) {
System.out.println("[저장된 사칙연산결과 조회]");
for (String s : super.getResultList()) {
System.out.println(s);
}
}else{
System.out.println("저장된 사칙연산결과가 없습니다.");
}
}
}
// Operator인터페이스
public interface Operator<T extends Number> {
T operate(T num1, T num2) throws BadInputException;
}
//AddOperator 클래스
public class AddOperator<T extends Number> implements Operator<T>{
public final Class<T> type;
public AddOperator(Class<T> type){
this.type = type;
}
@Override
public T operate(T firstNum, T secondNum){
double result = firstNum.doubleValue() + secondNum.doubleValue();
return NumberConversionUtils.convertNumberToType(result, type);
}
}
//NumberConversionUtils - number타입지정해주는 클래스
public class NumberConversionUtils {
@SuppressWarnings("unchecked")
public static <T extends Number> T convertNumberToType(Number result, Class<T> type){
if(type == Integer.class){
return(T)Integer.valueOf(result.intValue());
}else if(type == Double.class){
return(T)Double.valueOf(result.doubleValue());
}else if(type == Long.class){
return(T)Long.valueOf(result.longValue());
}else{
throw new IllegalArgumentException("지원하지않는 타입입니다." + type);
}
}
}
조건을 하나 추가해 입력받은 값보다 결과값이 더 큰 값들을 출력한다.
라고 요구사항을 작성하였는데 이를 풀어서 설명하자면 다음과 같다.
inquiry받던 것 처럼 하나 입력값을 받는데 그 값보다 사칙연산 결과값이 큰 연산결과만을 출력하는 것이다.
그런데 여기서 문제점이 있다.. 나는 튜터님처럼 result만 저장한 것이 아니라 연산과정까지 다 저장한 String이기 때문에 결과값만을 뽑아내는 식을 먼저 작성하고 비교할수 있도록 형변환도 해주어야한다.
여기서 사용한 메서드는 indexOf와 substring이다.
indexOf는 문자열에서 내가 원하는 문자가 몇번째 index에 있는지 찾아주는 메소드이며,
substring은 문자열을 잘라주는 역할을 한다.
(ex. result.substring(1)를 하면 result 문자열의 index 1부터 끝까지를 출력할수 있다.)
//ArithmeticCalculator 클래스 해당 메서드 추가
public void printResultGreaterThan(double num){
System.out.println("[결과값이 기준값보다 큰 연산결과]");
super.getResultList().stream().filter(result->Double.parseDouble(result.substring(result.indexOf("=")+2)) > num)
.forEach(System.out::println);
}
//App 클래스 inquiry아래에 추가
System.out.println("저장된 연산결과 중 입력한 값보다 큰 값들을 조회하시겠습니까? (lambda 입력시 조회)");
String lambda = sc.nextLine();
if (lambda.equals("lambda")) {
System.out.println("기준 값을 입력하세요: ");
double num = sc.nextDouble();
arithmeticCalculator.printResultGreaterThan(num);
}