어제 했던 것.

어제 간단한 계산기를 구현했다. 두개의 수와 사칙연산자를 입력받아, 연산을 수행하고 저장공간에 저장하고, 저장공간 제어까지.

남겨진 글과는 달라진 수정사항이 몇가지 있다.

System.out.println("사칙연산 기호를 입력하세요: ");
String operator = sc.nextLine();
---->
System.out.println("사칙연산 기호를 입력하세요: ");
char operator = sc.next().charAt(0);

과제 요구사항에 맞게 자료형을 String에서 char로 수정.

System.out.println("가장 먼저 저장된 연산 결과를 삭제하시겠습니까? (remove 입력 시 삭제)");
if(sc.nextLine().equals("remove")) {
       System.out.println("가장 먼저 저장된 "+numQ.peek()+" 를 삭제합니다.");
    }
System.out.println("저장된 연산결과를 조회하시겠습니까? (inquiry 입력 시 조회)");
if(sc.nextLine().equals("inquiry")) {
      System.out.println("저장된 연산결과를 불러옵니다.");
      for (int num : numQ) {
            System.out.println(num);
          }
    }
System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
if (sc.nextLine().equals("exit")) {
       System.out.println("계산기를 종료합니다.");
       flag = false;
   }

수행기능에 따른 출력문구를 추가,
연산결과 삭제시 remove() 메소드를 삭제와 동시에 반환도 해주는 peek()으로 대체.
이제 해보자. level 2

Level 2. 요구사항

1. 양의 정수 2개(0 포함)와 연산 기호를 매개변수로 받아 사칙연산(+,-,*,/) 기능을 수행한 후 결과 값을 반환하는 메서드와 연산 결과를 저장하는 컬렉션 타입 필드를 가진 Calculator 클래스를 생성합니다.

App.main 메소드의 연산 부분을 클래스로 따로 빼왔다.

public class Calulator {
   Queue<Double> numQ = new LinkedList<Double>();

   public double calculate(int firstNumber, int secondNumber, char operator) {
           // 결과값을 담을 변수 선언
           double result = 0;
           // 연산자에 따른 연산방법
           switch (operator) {
               case '+':
                   result = firstNumber + secondNumber;
                   break;
               case '-':
                   result = firstNumber - secondNumber;
                   break;
               case '*':
                   result = firstNumber * secondNumber;
                   break;
               case '/':
                   if (secondNumber != 0) {
                       result = firstNumber / secondNumber;
                   } else {
                       System.out.println("0으로 나눌수 없습니다.");
                       result = -1;
                   }
                   break;
           }
           // 연산방법 제어 끝
           numQ.add(result);
           return result;
       }
}

1.a. 예외처리하기


예외처리를 위한 클래스를 생성하고,

public class BadInputException extends Exception{
    public BadInputException(String type) {
        super("잘못된 입력입니다! " + type + "를 입력해주세요!");
    }
}

3주차 강의에서 봤던 클래스 코드를 가지고 왔다.

예외처리가 필요한 부분을 정리해보면,

App.main
	- 입력값이 정수가 아닐때
	- 입력값이 4가지 사칙연산 기호가 아닐때 (+, -, *, /)

Calculator
	- 나눗셈 연산일때 분모가 0일때

먼저 App.main의 입력 부분 예외처리.
사칙연산자는 4가지 문자를 저장한 List를 선언해 내장된 contain() 메소드를 이용해 날로먹었다.

public class App {
    public static void main(String[] args) throws Exception {
			...
            List<Character> operlandList = new ArrayList<Character>
            						(Arrays.asList(new Character[]{'+', '-', '*', '/'}));
            ... // 사칙연산자 후보리스트,
            
            System.out.println("첫 번째 숫자를 입력하세요: ");
            Integer firstNumber = sc.nextInt();
            if (firstNumber < 0 || !(firstNumber instanceof Integer)) {
                throw new BadInputException("정수");
            }
            System.out.println("사칙연산 기호를 입력하세요: ");
            Character operator = sc.next().charAt(0);
            if (!operlandList.contains(operator)) { // 후보리스트에 존재하지 않은 입력 값이면 false를 반환
                throw new BadInputException("사칙연산자");
            }
            System.out.println("두 번째 숫자를 입력하세요: ");
            Integer secondNumber = sc.nextInt();
            if (secondNumber < 0 || !(secondNumber instanceof Integer)) {
                throw new BadInputException("정수");
            }

이렇게 하면 일단은 예외처리를 할 수 있겠으나, 정수를 입력받는 부분은 이미 nextInt()로 받아 버리기 때문에 내 예외처리 보다 내부 예외처리가 먼저 작동해버린다.


그래도 사칙연산자 부분은 작동. 다행이다.

다음은 Calculator.calculate

...
 public double calculate (int firstNumber, int secondNumber, char operator) throws Exception{
 ...
 	switch(operator) {
    ...
    	case '/':
                    if (secondNumber != 0) {
                        result = firstNumber / secondNumber;
                    } else {
                        throw new BadInputException("0이 아닌 정수");
                    }
                    break;
            }
...


휴. 다행이다.

2. Level 1에서 구현한 App 클래스의 main 메서드에 Calculator 클래스가 활용될 수 있도록 수정합니다.

App.main에서 빠진 부분에는

public class App {
    public static void main(String[] args) {
...

        Calulator calulator = new Calulator();
        result = calulator.calculate(firstNumber, secondNumber, operator);

...
	}
}


다행히도 정상동작한다.

3. App 클래스의 main 메서드에서 Calculator 클래스의 연산 결과를 저장하고 있는 컬렉션 필드에 직접 접근하지 못하도록 수정합니다. (캡슐화)

먼저 Calculator의 Queue 필드를 private로 접근 제어

public class Calulator {
    private Queue<Double> numQ = new LinkedList<Double>();
    ...
  • Getter
    저장목록(Queue)를 그대로 반환하도록 했다.
public Queue<Double> getNumQ() {
        return numQ;
    }
  • Setter
    선입선출 형식의 저장방식을 그대로 가져왔다.
public void setNumQ(double result) {
        this.numQ.add(result);
    }

App.main() 에서의 수정.

...
           Calculator calculator = new Calculator();
           result = calculator.calculate(firstNumber, secondNumber, operator);
           calculator.setNumQ(result); // 저장을 setter 메소드로 대체
...
...
            System.out.println("저장된 연산결과를 조회하시겠습니까? (inquiry 입력 시 조회)");
            if(sc.nextLine().equals("inquiry")) {
                System.out.println("저장된 연산결과를 불러옵니다.");
                Queue<Double> numQ = calculator.getNumQ(); // 저장목록 Queue를 getter 메소드로 불러옴.
                for (double num : numQ) {
                    System.out.println(num);
                }
            }
...

4. Calculator 클래스에 저장된 연산 결과들 중 가장 먼저 저장된 데이터를 삭제하는 기능을 가진 메서드를 구현한 후 App 클래스의 main 메서드에 삭제 메서드가 활용될 수 있도록 수정합니다.

먼저 삭제와 동시에 값을 반환하는 Queue.peek() 메소드를 활용. 출력문까지 메소드에 포함시켰다.

Calculator.calculate
public void removeNumQ() {
        System.out.println("가장 먼저 저장된 "+ this.numQ.peek() +" 를 삭제합니다.");
    }
//이에 따른 App.main에서의 수정
App.main
System.out.println("가장 먼저 저장된 연산 결과를 삭제하시겠습니까? (remove 입력 시 삭제)");
if(sc.nextLine().equals("remove")) {
      calculator.removeNumQ();
      }

5. Calculator 클래스에 저장된 연산 결과들을 조회하는 기능을 가진 메서드를 구현한 후 App 클래스의 main 메서드에 조회 메서드가 활용될 수 있도록 수정합니다.

getter메소드를 활용한 기존 조회 부분을 calculator 클래스로 이동 및 수정.

Calculator.calculate
    public void searchNumQ() {
        System.out.println("저장된 연산결과를 불러옵니다.");
        Queue<Double> numQ = this.getNumQ();
        for (double num : numQ) {
            System.out.println(num);
        }
    }
//이에 따른 App.main에서의 수정
App.main
System.out.println("저장된 연산결과를 조회하시겠습니까? (inquiry 입력 시 조회)");
    if(sc.nextLine().equals("inquiry")) {
         calculator.searchNumQ();
    }

6. Calculator 인스턴스를 생성(new)할 때 생성자를 통해 연산 결과를 저장하고 있는 컬렉션 필드가 초기화 되도록 수정합니다.

public class Calculator {
    private Queue<Double> numQ;

    public Calculator() {
        this.numQ = new LinkedList<>();
    }

하... 나왔다... 큰 장벽... 이게 맞을까? 싶은 구간에 돌입해벌임..
일단은 뒤적뒤적 해본 결과 이걸로 작성했다.

7. Calculator 클래스에 반지름을 매개변수로 전달받아 원의 넓이를 계산하여 반환해주는 메서드를 구현합니다.

원넓이 공식이 헷갈렸지만, 일단은 어찌저찌 구현.

static double radius;
// 한번 입력된 반지름 값은 변하지 않음 -> static
final static double PI = 3.14159265358979323846; 
//파이 값은 절대로 변하지 않을 상수. -> final static

public double calculateCircleArea(double radius) {
        this.radius = radius;
        return PI * radius * radius;
    }

작성하면서도 이게 맞을까? 왜 굳이 위에서 선언해야하지? 라는 생각이 있지만... 그치만 이렇게 하지 않으면 어떻게 쓰는지 모르겠는걸..

캡슐화 메소드들은 위의 numQ의 것들을 가져왔다.

   public Queue<Double> getCircleQ() {
        return circleQ;
    }

    public void setCircleQ(double result) {
        this.circleQ.add(result);
    }

    public void removeCircleQ() {
        System.out.println("가장 먼저 저장된 " + this.circleQ.peek() + " 를 삭제합니다.");
    }

    public void searchCircleQ() {
        System.out.println("저장된 연산결과를 불러옵니다.");
        for (double num : circleQ) {
            System.out.println(num);
        }

App.main 메소드에는 사칙연산, 원넓이계산 두가지 경우를 switch..case 문으로 두가지 분기.

System.out.println("사칙연산(1) 원넓이계산(2) : ");
            Integer calChoice = sc.nextInt();
            switch (calChoice) {
                case 1 :
                
                ....
                
                case 2 :
                
                ....

콘솔창 결과.

사칙연산(1) 원넓이계산(2) : 
2
원의 반지름을 입력하세요: 
10.4
결과: 339.79466141227203
가장 먼저 저장된 연산 결과를 삭제하시겠습니까? (remove 입력 시 삭제)
저장된 연산결과를 조회하시겠습니까? (inquiry 입력 시 조회)
inquiry
저장된 연산결과를 불러옵니다.
339.79466141227203
더 계산하시겠습니까? (exit 입력 시 종료)

사칙연산(1) 원넓이계산(2) : 
2
원의 반지름을 입력하세요: 
12
결과: 452.3893421169302
가장 먼저 저장된 연산 결과를 삭제하시겠습니까? (remove 입력 시 삭제)
저장된 연산결과를 조회하시겠습니까? (inquiry 입력 시 조회)

더 계산하시겠습니까? (exit 입력 시 종료)

사칙연산(1) 원넓이계산(2) : 
1
첫 번째 숫자를 입력하세요: 
2
사칙연산 기호를 입력하세요: 
+
두 번째 숫자를 입력하세요: 
15
결과: 17.0
가장 먼저 저장된 연산 결과를 삭제하시겠습니까? (remove 입력 시 삭제)

저장된 연산결과를 조회하시겠습니까? (inquiry 입력 시 조회)
inquiry
저장된 연산결과를 불러옵니다.
17.0
더 계산하시겠습니까? (exit 입력 시 종료)

사칙연산(1) 원넓이계산(2) : 
2
원의 반지름을 입력하세요: 
116
결과: 42273.27074670426
가장 먼저 저장된 연산 결과를 삭제하시겠습니까? (remove 입력 시 삭제)
저장된 연산결과를 조회하시겠습니까? (inquiry 입력 시 조회)
inquiry
저장된 연산결과를 불러옵니다.
339.79466141227203
452.3893421169302
42273.27074670426
더 계산하시겠습니까? (exit 입력 시 종료)
exit
계산기를 종료합니다.

종료 코드 0(으)로 완료된 프로세스

오 되는데?

8. 사칙연산을 수행하는 계산기 ArithmeticCalculator 클래스와 원과 관련된 연산을 수행하는 계산기 CircleCalculator 클래스 2개를 구현합니다.


만들긴 했는데, 본래 Calculator 안에서 선언되어 있던 각각의 메소드들을 옮겨봤다.
CircleCalculator

import java.util.LinkedList;
import java.util.Queue;

public class CircleCalculator extends Calculator {

    private Queue<Double> circleQ;
    static double radius;
    final static double PI = 3.14159265358979323846;

    public CircleCalculator() {
        this.circleQ = new LinkedList<>();
    }

    public double calculate(double radius) {
        this.radius = radius;
        result = PI * radius * radius;
        return result;
    }

    public Queue<Double> getCircleQ() {
        return circleQ;
    }

    public void setCircleQ(double result) {
        this.circleQ.add(result);
    }

    public void removeCircleQ() {
        System.out.println("가장 먼저 저장된 " + this.circleQ.peek() + " 를 삭제합니다.");
    }

    public void searchCircleQ() {
        System.out.println("저장된 연산결과를 불러옵니다.");
        for (double num : circleQ) {
            System.out.println(num);
        }
    }
}

ArithmeticCalculator

public class ArithmeticCalculator extends Calculator {
    private Queue<Double> numQ;

    public ArithmeticCalculator() {
        this.numQ = new LinkedList<>();
    }
    
    public double calculate(int firstNumber, int secondNumber, char operator) throws Exception {
        // 연산자에 따른 연산방법
        switch (operator) {
            case '+':
                result = firstNumber + secondNumber;
                break;
            case '-':
                result = firstNumber - secondNumber;
                break;
            case '*':
                result = firstNumber * secondNumber;
                break;
            case '/':
                if (secondNumber != 0) {
                    result = firstNumber / secondNumber;
                } else {
                    throw new BadInputException("0이 아닌 정수");
                }
                break;
        }
        // 연산방법 제어 끝
        return result;
    }


    public Queue<Double> getNumQ() {
        return numQ;
    }

    public void setNumQ(double result) {
        this.numQ.add(result);
    }

    public void removeNumQ() {
        System.out.println("가장 먼저 저장된 " + this.numQ.peek() + " 를 삭제합니다.");
    }

    public void searchNumQ() {
        System.out.println("저장된 연산결과를 불러옵니다.");
        for (double num : numQ) {
            System.out.println(num);
        }
    }
}

본래의 Calculator 클래스를 상속받아야 해서 상속문을 작성하긴 했는데 자비없이 그어지는 빨간 밑줄.

Calculator 클래스는 이렇게 추상 클래스로 긴급수정에 들어갔지만 여전히 남아있다.

public abstract class Calculator {
    static double result;
    public abstract double calculate();
}

큰일이다. 진짜 너무 막혔다. 전혀 모르겠으니 Git 연동도 할겸 처음부터 시작하는 마음으로 해보자.

일단 class 별로 나눴던 메서드들을 다시 Calculator로 모으고 삭제해보자.
추상화는 시기상조라고 생각. 일단 사칙연산, 원넓이계산 두가지 클래스만 구현에 성공했다.

// ArithmeticCalculator.class
public class ArithmeticCalculator {
    public double operate(double firstNumber, double secondNumber, char operator) throws Exception {
        // 연산자에 따른 연산방법
        switch (operator) {
            case '+':
                return firstNumber + secondNumber;
            case '-':
                return firstNumber - secondNumber;
            case '*':
                return firstNumber * secondNumber;
            case '/':
                if (secondNumber != 0) {
                    return firstNumber / secondNumber;
                } else {
                    throw new BadInputException("0이 아닌 정수");
                }
            default: throw new BadInputException("사칙연산자");
        }

        // 연산방법 제어 끝
    }
// CircleCalculator.class
public class CircleCalculator {
    // 원넓이
    static final double PI = 3.14159265358979323846;

    public double operate(double radius) {
        return PI * radius * radius;
    }
}

이 두가지를 구현하면서 Calculator.class의 연산부분을 뺐고, 두가지 클래스를 불러오고 메소드를 추가했다.

public class Calculator {
...
    private final ArithmeticCalculator arithCalc = new ArithmeticCalculator();
    private final CircleCalculator circleCalc = new CircleCalculator();
...
    public double arithC (double a, double b, char c) throws Exception {
        return this.arithCalc.operate(a, b, c);
    }

    public double circleC (double a) {
        return this.circleCalc.operate(a);
    }
...
}

결과는 정상작동했다ㅠㅠㅠㅠㅠㅠ

9. ArithmeticCalculator 클래스의 연산 메서드에 책임(역할)이 많아 보입니다. 사칙연산 각각의 기능을 담당하는 AddOperator, SubtractOperator, MultiplyOperator, DivideOperator 클래스를 만들어 연산 메서드의 책임을 분리 해봅니다. (SRP)

두가지도 힘들었는데 이제는 4가지로 분리해야한다.

public class AddOperator {
    public double operate(double a, double b) {
        return a + b;
    }
}
public class SubtractOperator {
    public double operate(double a, double b) {
        return a - b;
    }
}
public class MultiplyOperator {
    public double operate(double a, double b) {
        return a * b;
    }
}
public class DivideOperator {
    public double operate(double a, double b) throws Exception {
        if (b == 0) {
            throw new BadInputException("0이 아닌 정수");
        }
        return a / b;
    }
}

하지만 일단은 이렇게 했더니 쉬운것 같기도 하고, 그래서 걱정되기도 하고 그렇다.
이에 따라 ArithmeticCalculator.class도 수정.

public class ArithmeticCalculator {

    private final AddOperator add = new AddOperator();
    private final SubtractOperator sub = new SubtractOperator();
    private final MultiplyOperator mul = new MultiplyOperator();
    private final DivideOperator div = new DivideOperator();

    public double operate(double firstNumber, double secondNumber, char operator) throws Exception {
        // 연산자에 따른 연산방법
        switch (operator) {
            case '+':
                return add.operate(firstNumber, secondNumber);
            case '-':
                return sub.operate(firstNumber, secondNumber);
            case '*':
                return mul.operate(firstNumber, secondNumber);
            case '/':
                return div.operate(firstNumber, secondNumber);
            default: throw new BadInputException("사칙연산자");
        }

        // 연산방법 제어 끝
    }
}

일단 결과는 다행스럽게도 정상작동.

10. ArithmeticCalculator 클래스에 추가로 나머지 연산(%) 기능을 추가하기 위해 ModOperator 클래스를 만들어 추가합니다.

나머지 연산 추가는 간단하니 생략, 문제는 객체지향원칙에 따른 코드수정인데, 오늘은 여기까지 하고 새벽에 이어서 고민을 해봐야겠다.

profile
둘뺌

0개의 댓글

관련 채용 정보