다음주 수요일까지 제출해야 하는 과제가 생겼다.
이름하여 계산기 만들기. 그렇다 계산기를 만들면 된다.
물론 현실에서 만드는 건 아니고 JAVA를 사용해 만들면 된다.
총 LV3까지 있으며 1,2는 필수, 3레벨은 선택이다. 숙제의 내용은 너무 길어 대충 요약하자면 다음과 같다.
- LV1
정수의 사칙연산이 가능한 계산기를 만든다.
두 수와 연산자를 입력받아 결과를 출력하면 된다.
또한 계산이 끝난 후 'exit'을 입력할 때까지 반복하면 된다.
- LV2
실수의 사칙연산이 가능한 계산기를 만든다.
LV1의 기능을 포함하며 객체 지향적으로 만들어야 한다.
결과값들을 저장하고 첫 번째 결과값을 삭제하는 기능을 추가한다.
- LV3
LV2의 내용을 모두 포함한다.
generic을 사용해 실수와 정수를 유동적으로 연산할 수 있게 한다.
결과값을 조회하는 기능을 추가한다.
lamda와 stream을 활용해 결과값보다 큰 수를 조회하는 기능을 추가한다.기한은 다음 주 수요일이지만 어림도 없지 당장 해버리기로 했다.
이건 솔직히 너무 많이 짜본 코드이기도 하고, 쉽기도 하고 해서 슥슥 딸깍 해서 금방 해치워버렸다.
코드는 다음과 같다.import java.util.Scanner; public class Calculator1 { public void Calculate() { int num1, num2; char op; String re = ""; while (!re.equals("exit")) { Scanner sc = new Scanner(System.in); sout("Input first number!"); num1 = sc.nextInt(); num1 = num1 < 0 ? -num1 : num1; sout("Input operator!"); op = sc.next().charAt(0); sout("Input second number!"); num2 = sc.nextInt(); num2 = num2 < 0 ? -num2 : num2; sout("result = " + calc(num1, op, num2)); sout("Baby one more time? (write exit to exit)"); re = sc.next(); } } private void sout(String s) { System.out.println(s); } private int calc(int num1, char op, int num2) { switch (op) { case '+': return num1 + num2; case '-': return num1 - num2; case '*': return num1 * num2; case '/': return num1 / num2; default: return 0; } } }sout를 굳이 메서드로 구현 해 놓은 이유는 앞으로 머신러닝 코드 자동 완성 기능을 사용하지 말라는 지시가 있어서 여러 번 적기 귀찮아 만든건데, 나중에 보니 sout는 그냥 단축키같은 거라 그냥 써지더라. 한마디로 뻘짓이다.
반복문 내부에서 무한 입출력을 하다 'exit'이 입력되면 탈출하는 구조이다.
결과의 절댓값을 출력하는 이유는 양의 정수에 대한 계산기라 혹여나 음의 정수가 들어오면 이를 배제하기 위함이다.
갑자기 확 귀찮아져 버렸지만, 난이도 자체가 그리 높은 편은 아니었다.
import java.util.Scanner; public class App { public void Calculate() { double num1, num2; char op; String re = ""; Scanner sc = new Scanner(System.in); Calculator calculator = new Calculator(); while (!re.equalsIgnoreCase("exit")) { sout("Input first number: "); num1 = sc.nextDouble(); sout("Input operator (+, -, *, /): "); op = sc.next().charAt(0); sout("Input second number: "); num2 = sc.nextDouble(); try { double result = calculator.calculate(num1, op, num2); sout("Result: " + result); } catch (ArithmeticException e) { sout("Error: " + e.getMessage()); } sout("Wanna see all results? (yes/no)"); if (sc.next().equalsIgnoreCase("yes")) { sout("History: " + calculator.getResults()); } sout("Wanna kill the oldest result? (yes/no)"); if (sc.next().equalsIgnoreCase("yes")) { calculator.deleteOldestResult(); sout("You killed oldest result."); } sout("Baby one more time? (type 'exit' to quit)"); re = sc.next(); } sc.close(); } private void sout(String s) { System.out.println(s); } }전반적으로 LV1과 동일하나, 과제의 요구사항대로 결과값의 저장,조회,삭제를 구현했고, 실질적인 연산이 일어나는 부분과 함께 따로 빼서 구현했다.
import java.util.ArrayList; import java.util.List; public class Calculator { private List<Double> results; public Calculator() { results = new ArrayList<>(); } public double calculate(double num1, char operator, double num2) { double result = 0; switch (operator) { case '+': result = num1 + num2; break; case '-': result = num1 - num2; break; case '*': result = num1 * num2; break; case '/': if (num2 == 0) { throw new ArithmeticException("Can't divide by zero."); } result = num1 / num2; break; default: System.out.println("Invalid operator. Use +, -, *, /"); return 0; } results.add(result); // Store the result return result; } // Getter for the results list (returns a copy for encapsulation) public List<Double> getResults() { return new ArrayList<>(results); } // Method to delete the oldest result public void deleteOldestResult() { if (!results.isEmpty()) { results.remove(0); } else { System.out.println("No results to delete."); } } }사실 조회는 3레벨인데 착각하고 미리 구현해버렸다.
주석을 영어로 적은 이유는 있어보이기 위해서이다.
레벨3은 객체지향적으로 코드를 쪼개버렸고, 요구사항대로 lamda, stream, generic등을 사용했다.
3레벨은 클래스가 너무 많아 도무지 여기에 옮길 수 없어 깃허브 링크를 올리기로 했다.
그럼 처음부터 링크만 띡 올리면 되는 거 아니냐는 생각이 이제와서 들긴 하는데, 너무 성의없는 것 같기도 하고 이왕 적은 거 굳이 지우지는 않기로 했다.대충 설명하자면, 연산자를 열거형으로 바꾼 뒤 Operator 인터페이스를 만들어 실질적인 연산을 담당할 operate를 선언해놓았다.
이후 각 연산을 모두 클래스로 만들어 Operator를 상속받고 연산을 구현, 열거형으로 바꿔놓은 연산자에 따라 해당 클래스를 호출해 사용하는 방식이다.
이렇게 하면 나중에 새로운 기능(ex: 제곱, 제곱근, a^b승 등)을 추가하기가 용이해진다.
나중에 시간나면 해볼까 싶지만 지금은 귀찮은 관계로 pass~
사실 글만 봐도 알 수 있듯이, 계산기 자체를 만드는 데에는 큰 어려움은 없었다.
다만 문제가 있다면 이 계산기는 결국 과제이기에 만든 것이고, 과제의 요구사항을 따라야 한다는 것이다.
물론 그 자체가 문제가 되지는 않지만, 내용은 또 다른 얘기이다. LV3를 보면 generic을 사용해 실수값도 문제없이 전달받을 수 있게 하라는 요구가 있는데, 사실 썩 내키지는 않았다.
물론 초보자들에게 generic을 경험 해 보라는 의미로 넣은 사항이겠지만, 단지 실수와 정수를 모두 받기 위해서라면 다른 좋은 방법이 많기 때문이다.
그래서 이 부분에서 '과제의 내용이 generic을 사용하는 것이니 그대로 행할 것이냐, 이를 거부하고 더 좋은 방법으로 실수를 받아 연산을 할 것이냐' 하는 고민을 했다.
결론적부터 말하자면 전자의 상황으로 가게 되었지만 의도는 그렇지 않았다.
generic을 쓰라는 상황에 맞게, 어찌됐던 사용은 해야겠다는 생각이 들어서 실수를 받을 때 쓰는 대신 더 좋은 곳에 쓸 수 있을까 하는 생각을 오래 했다.
하지만 내 머리에서는 도무지 generic을 더 좋게 사용할 부분을 찾을 수 없었다.
그래서 어떻게든 쓰겠다고 온몸비틀기를 해가며 내놓은 결과가 결국 과제의 요구사항대로 되었다.
이걸 쓰면서 느끼는 점은, 이런 상황이 앞으로 있을까 싶은 생각도 조금 들고, 이게 트러블인지도 의문이고, 슈팅을 한 건지도 의문이다. 하지만 이런 것을 제외한다면 별다른 트러블이 없었다는 것은 오히려 좋은 상황이 아닌가 싶은 생각이 든다.