오늘은 계산기 2레벨을 만들었다.
계산기 과제 2레벨의 요구사항은 클래스를 적용하여 기본적인 연산을 수행할 수 있는
계산기를 만드는거였다. 아래 클래스들처럼 operate()라는 추상화 메서드를 만든 추상화 클래스를
각 클래스가 상속 받게하여 오버라이딩으로 각 연산기호에 맞는 연산을 하게 했다.
// 연산 추상화 클래스
public abstract class AbstOperation {
public abstract double operate(double firstNumber, double secondNumber);
}
// 덧셈 연산
public class AddOperation extends AbstOperation{
@Override
public double operate(double firstNumber, double secondNumber) {
return firstNumber + secondNumber;
}
}
// 뺄셈 연산
public class SubstractOperaction extends AbstOperation{
@Override
public double operate(double firstNumber, double secondNumber) {
return firstNumber - secondNumber;
}
}
// 곱셈 연산
public class MultiplyOperation extends AbstOperation{
@Override
public double operate(double firstNumber, double secondNumber) {
return firstNumber * secondNumber;
}
}
// 나누기
public class DivideOperation extends AbstOperation{
@Override
public double operate(double firstNumber, double secondNumber) {
return firstNumber / secondNumber;
}
}
App 클래스
계산기를 실행하는 main 메서드가 있는 App 클래스
import java.util.Scanner;
public class App {
// appStart()안에서 CalculatorApp 클래스를 객체화 하고 숫자타입이나 연산자 기호 타입을 잘못 쳤을때 예외 발생으로
// 반복문의 처음으로 돌아가 다시 시작할 때 리스트안의 내용들이 다 날라가는데
// 클래스 필드의 선언해두고 실행을하면 예외 발생으로 반복문의 처음으로 돌아가 다시 시작해도 리스트 안의 내용들이 그대로 남아있다.
// exit로 실행을 중지 하기 전까지 데이터들이 리스트안에 계속 쌓인다.
// exit로 실행을 중지 하고 다시 재실행하면 리스트 데이터들은 다시 리셋된다.
CalculatorApp app = new CalculatorApp();
public static void main(String[] args) {
boolean mainFlag = false;
App apple = new App();
//처음에 mainFlag에 false를 기본값으로 주고
// app.appStart 메서드에서 마지막에 exit를 입력하면
// return true를 받게 하여 while문 탈출.
// 그 외에 예외처리나 exit를 입력하지 않을 시에는 계속 반복.
while(!mainFlag) {
try{
mainFlag = apple.appStart();
}
catch(Exception e){
System.out.println(e.getMessage());
}
}
}
public boolean appStart() throws Exception {
Scanner sc = new Scanner(System.in);
// exit를 입력받으면 flag를 false로 바꿔 while문 종료.
boolean flag = true;
while (flag) {
// 숫자가 아닌 다른것을 쳤을 때 예외 처리
System.out.print("숫자 입력 : ");
String firstNumber = sc.nextLine();
app.setFirstNumber(firstNumber);
System.out.print("사칙연산 기호 입력(+,-,*,/) : ");
String operation = sc.nextLine();
app.setUpOperation(operation);
System.out.print("숫자 입력 : ");
String secondNumber = sc.nextLine();
app.setSecondNumber(secondNumber);
// 0으로 나누기를 시도 할시에 이 if문 안으로 들어와 출력문 수행
if ((firstNumber.equals("0") || secondNumber.equals("0")) && operation.equals("/")) {
System.out.println("0으로는 나눌 수 없습니다.");
continue;
}
System.out.println("결과 : " + app.calculator());
System.out.print("지금까지 계산하신 결과값들을 보시겠습니까 ? (Y, N) : ");
String resultAnswer = sc.nextLine();
// Y or y 입력시 아래 if문 실행
if (resultAnswer.equals("Y") || resultAnswer.equals("y")) {
app.getArrNumbersAll();
System.out.print("수정하고 싶은 번호가 있으신가요 ? (Y, N) : ");
String setAnswer = sc.nextLine();
// Y, y 이외에 다른 것 을 치면 아래로 내려가
// 다시 시작하거나 exit를 입력하여 while문 탈출
if (setAnswer.equals("Y") || setAnswer.equals("y")) {
app.getArrNumbersAll();
System.out.print("수정하고 싶으신 번호의 번호를 입력해주세요 : ");
String targetNumber = sc.nextLine();
System.out.print("뭐로 수정 하실건가요 ? : ");
String changeNumber = sc.nextLine();
app.setArrNumber(targetNumber, changeNumber);
app.getArrNumbersAll();
System.out.print("맨 처음에 있는 숫자 삭제하실건가요 ? (Y, N) : ");
String removeAnswer = sc.nextLine();
// Y, y 이외에 다른 것 을 치면 아래로 내려가
// 다시 시작하거나 exit를 입력하여 while문 탈출
if (removeAnswer.equals("Y") || removeAnswer.equals("y")) {
app.removeArrNumber();
app.getArrNumbersAll();
}
}
}
System.out.print("더 계산하시겠습니까 ? (exit 입력시 종료) : ");
String answer = sc.nextLine();
// 입력된 값이 exit라면 true를 return하여
// main 메서드의 while문 탈출.
if (answer.equals("exit")) {
return true;
}
}
// 그게 아니라면 계속 반복.
return false;
}
}
Calculator 클래스
들어온 연산 기호에 맞게 AbstOperation 타입의 operation의 담아주고
연산 메서드를 실행하고 그 연산된 결과를 리스트에 저장
import calculatingMachine.lv2.operation.*;
import java.util.ArrayList;
public class Calculator {
private final ArrayList<Double> arrNumbers = new ArrayList<Double>();
private AbstOperation operation;
public void setOperation(AbstOperation operation) {
this.operation = operation;
}
// 들어온 연산 기호에 따라 operation을 담는다.
public void setUpOperation(String operation) {
switch (operation) {
case "+":
setOperation(new AddOperation());
break;
case "-":
setOperation(new SubstractOperaction());
break;
case "*":
setOperation(new MultiplyOperation());
break;
case "/":
setOperation(new DivideOperation());
break;
}
}
// 연산 후 연산 결과 리스트에 저장 및 전달.
public double calculator(double firstNumber, double secondNumber) {
double result = operation.operate(firstNumber, secondNumber);
arrNumbers.add(result);
return result;
}
// arrNumbers 리스트에 저장된 모든 값 전달
public void getArrNumbersAll() {
int num = 1;
for (Double arrNumber : arrNumbers) {
System.out.println(num+". "+arrNumber);
num++;
}
}
// 원하는 방번호의 값 전달
public double getArrNumber(int idxNumber) {
return arrNumbers.get(idxNumber);
}
// arrNumbers 리스트에 저장된 값 수정
public void setArrNumbers(int idxNumber, double changeNumber) {
arrNumbers.set(idxNumber, changeNumber);
}
// arrNumbers 리스트의 저장된 맨 처음 값 삭제
public void removeArrNumber() {
// 컬렉션에 저장된 데이터가 하나도 없을때는 더 이상 삭제할 데이터가 없다는 문구 출력
if (arrNumbers.isEmpty()) {
System.out.println("더 이상 삭제할 데이터가 없습니다.");
}
// 데이터가 하나 이상있다면 제일 먼저 저장된 0번방의 데이터를 삭제 후
// 삭제 완료 문구 출력
else {
arrNumbers.remove(0);
System.out.println("삭제 완료 !");
}
}
}
CalculatorApp 클래스
Main 메서드에서 입력 받은 값들이 알맞은 타입으로 들어왔는지 확인하여 Calculator 메서드를 실행하거나
예외처리를 해준다.
import java.util.regex.Pattern;
public class CalculatorApp {
private final Calculator calculator = new Calculator();
private static final String OPERATION_REG = "[+\\-*/]";
private static final String NUMBER_REG = "^[0-9]*$";
private static final String Double_REG = "^([0-9]{1}\\d{0,2}|0{1})(\\.{1}\\d{0,1})*$";
private double firstNumber;
private double secondNumber;
public void setOperation(String operation) {
calculator.setUpOperation(operation);
}
// 입력받아온 숫자가 숫자형식인지 정규식으로 비교한 후 숫자가 맞다면
// double 형태로 형변환을 하는 동시에 firstNumber에 값을 저장.
public void setFirstNumber(String firstNumber) throws Exception {
if(Pattern.matches(Double_REG,firstNumber)){
this.firstNumber = Double.parseDouble(firstNumber);
}
else{
throw new BadInputException("정수, 실수");
}
}
// 입력받아온 숫자가 숫자형식인지 정규식으로 비교한 후 숫자가 맞다면
// double 형태로 형변환을 하는 동시에 secondNumber에 값을 저장.
public void setSecondNumber(String secondNumber) throws Exception {
if(Pattern.matches(Double_REG,secondNumber)){
this.secondNumber = Double.parseDouble(secondNumber);
}
else{
throw new BadInputException("정수, 실수");
}
}
// 리스트의 저장된 모든 값을 가져옴
public void getArrNumbersAll(){
calculator.getArrNumbersAll();
}
// 입력 받은 연산 기호가 연산기호 이외에 다른 문자가 아닌지 판별 후
// 우리가 정해둔 연산기호가 맞다면 setUpOperation 메서드를 실행.
// 아니라면 예외 처리
public void setUpOperation(String operation) throws Exception{
if(Pattern.matches(OPERATION_REG, operation)){
calculator.setUpOperation(operation);
}
else{
throw new BadInputException("연산 기호");
}
}
// 받아온 인덱스 번호의 값 전달해주는 메서드 실행.
public double getArrNumber(int idxNumber){
return calculator.getArrNumber(idxNumber);
}
// 연산과 연산 결과 리스트의 저장하는 메서드 실행
public double calculator(){
return calculator.calculator(this.firstNumber,this.secondNumber);
}
// 받아온 입력값이 정수or실수가 맞는지 확인 후 맞다면 리스트 값을 수정해주는 set 메서드 실행
// 아니라면 예외처리
public void setArrNumber(String targetNumber, String changeNumber) throws Exception{
if(Pattern.matches(NUMBER_REG, targetNumber)){
if(Pattern.matches(Double_REG, changeNumber)){
int idxNumber = Integer.parseInt(targetNumber)-1;
calculator.setArrNumbers(idxNumber, Double.parseDouble(changeNumber));
}
else{
throw new BadInputException("정수");
}
}
else{
throw new BadInputException("정수");
}
}
// 가장 맨 앞에 저장되어 있는 데이터 삭제하는 메서드 실행
public void removeArrNumber(){
calculator.removeArrNumber();
}
}
마지막으로 예외처리를 하기위해 만든 BadInputException
public class BadInputException extends Exception{
public BadInputException(String e) {
super(e+"를 잘 입력해주세요");
}
}
이런식으로 마침내 계산기 2레벨도 만들었다!!!
그런데 만들면서 제일 헷갈렸던 점이 있었는데 private static final을 어떨 때 써야할지 또, private final은 또 어떨 때 써야할지였다... 그래서 검색도 해보고 했지만 머릿속에 잘 들어오지 않아 머리에 넣을 겸 정리를 해봤다..!! 이 쯤되면 머릿속에 좀 들어와주라..
상수
프로그래밍 언어에서 상수는 변하지 않아야 할 데이터를 임시적으로 저장하기 위한 수단으로 사용된다.
final
자바에서는 상수를 구현하기 위해 final이라는 키워드를 사용한다. final은 해당 오브젝트를 단 한번만 할당 할 수 있음을 의미한다. 보통 상수를 선언할 때 static final을 사용하여 상수를 선언하게 됩니다.
static
static은 '정적인', '움직임이 없는' 이라는 의미로, static을 사용하면 JVM의 static 메모리에 올라간다. 즉, static 데이터는 프로그램 실행 직후 부터 끝날 때까지 메모리 수명이 유지된다.static 메모리에 올라가기 때문에 초기화 과정 필요없이 static이 선언된 변수, 메서드에 바로 접근이 가능하다.static을 사용한다는 의미는 해당 객체를 공유하겠다는 의미이며, 다른 곳에서 해당 객체를 사용한다면 그 객체는 항상 동일한 객체라는 뜻이다.
private
private 제어자는 해당 멤버를 선언한 클래스 내에서만 접근할 수 있도록 제한한다. 따라서 외부 클래스나 인스턴스에서는 접근할 수 없다. 해당 클래스의 내부 구현에만 사용되는 것이 일반이다.
private static final
private static final을 선언한 변수를 사용하면 재할당하지 못하며, 메모리에 한 번 올라가면 같은 값을 클래스 내부의 전체 필드, 메서드에 공유한다.
private final
private final을 선언한 변수를 사용하면 재할당하지 못하며, 해당 필드, 메서드 별로 호출할 때마다 새로이 값이 할당(인스턴스화)한다.
마무리
계산기 레벨3에서는 Enum과 제네릭, 람다 & 스트림을 이용하여 만드는건데 만들 수 있을지는 모르겠지만
주말에 공부를 좀 해서 만들기 시도를 해봐야겠다!
아자아자
'TIL' 카테고리의 다른 글
[TIL] 9월 13일 (1) | 2024.09.13 |
---|---|
[TIL] 숫자 야구 게임 만들기 (2) | 2024.09.12 |
[TIL] 계산기 3레벨 마무리 (1) | 2024.09.10 |
[TIL] 계산기 3레벨 해보기 (6) | 2024.09.09 |
[TIL] 오늘 (7) | 2024.09.04 |