계산기 만들기 최종

이상민·2024년 7월 30일
0
post-thumbnail

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

Calculator

package calculator;

import java.util.LinkedList;

public class Calculator {// 연산 결과를 저장하고 있는 컬렉션 필드에 직접 접근하지 못하도록 수정
    private LinkedList<Integer> resultList; // private 접근 제어자 사용
    // 연산 결과를 저장하기 위한 컬렉션 선언 및 생성
    private LinkedList<Double> circleArea; // 원의 넓이 결과를 저장하는 필드 선언
    private static final double Pi = 3.14; // 원의 지름 값은 3.14로 변하지 않는 값이기 때문에
    // static final을 이용하여 프로그램에서 변경되지 않는 값을 정의

    // 6. 생성자를 통해 연산 결과를 저장하고 있는 컬렉션 필드가 초기화 되도록 수정
    public Calculator() {
        resultList = new LinkedList<>(); // 생성자에서 컬렉션 필드를 초기화, 항상 빈리스트로 시작
        circleArea = new LinkedList<>(); // 원의 넓이 결과 필드 초기화
    }

    public LinkedList<Integer> getResultList() { //resultList getter 메서드 이름의 규칙 : get + 필드 이름
        return resultList;
    }

    public void setResultList(LinkedList<Integer> resultList) { // setter 규칙 : set + 필드 이름
        this.resultList = resultList;
    }

    public LinkedList<Double> getCircleArea() { // circleResult getter
        return circleArea;
    }

    public void setCircleArea(LinkedList<Double> circleArea) { // setter
        this.circleArea = circleArea;
    }

    public int calculate(int firstNumber, int secondNumber, char operator) throws InputErrorException {
        int result = 0;
        switch (operator) {
            case '+' : // 사칙 연산을 수행 후 result에 저장
                result = firstNumber + secondNumber;
                break;
            case '-' :
                result = firstNumber - secondNumber;
                break;
            case '*' :
                result = firstNumber * secondNumber;
                break;
            case '/' :
                if (secondNumber == 0)  // 나눗셈 연산에서 두번째 정수에 0이 입력되는 경우 Exception 발생
                    throw new InputErrorException("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다.");

                else
                    result = firstNumber / secondNumber;
                break;
        }
                return result; // 사칙연산된 결과값을 리턴
    }

    // 4. Calculator 클래스에 저장된 연산 결과들 중
    // 가장 먼저 저장된 데이터를 삭제하는 기능을 가진 메서드를 구현한 후 App 클래스의 main 메서드에 삭제 메서드가 활용될 수 있도록 수정
    public void removeResultList() { // 공용 메서드 선언 , 반환값은 없고(void), 파라미터도 받지 않는다.
        resultList.remove(0);
    }
    // 5.Calculator 클래스에 저장된 연산 결과들을 조회하는 기능을 가진 메서드를 구현한 후
    // App 클래스의 main 메서드에 조회 메서드가 활용될 수 있도록 수정합니다.
    public void inquiryResultList() { // 공용 메서드 선언
        for (int results : resultList) { //리스트에 저장된 배열의 수만큼 순회하면서 반복
            System.out.print(results + " "); // 결과 값을 한칸씩 띄어주기 위해서
        }
        System.out.println(); // 조회 값과 exit문이 겹쳐서 나오는 것을 막기 위해서
    }
    // 7. Calculator 클래스에 반지름을 매개변수로 전달받아 원의 넓이를 계산하여 반환해주는 메서드를 구현
    public double calculateCircleArea(int radius) { // 원의 넓이를 구하는 메서드, 매게변수 정수 반지름을 받는다
        return Pi * radius * radius; // 반지름* 반지름 *pi = 원의 넓이
    }
    // 원 넓이 조회
    public void inquiryCircleArea() {
        for (double results : circleArea) {
            System.out.print(results + " ");
        }
        System.out.println();
    }

}

해설

private LinkedList<Double> circleArea;
원의 넓이 값은 실수형으로 나오므로 Double 객체를 저장하는 원의 넓이 결과를 저장하는 필드를 생성
private static final double Pi = 3.14;
파이값은 변하지 않는 값이기 때문에 final로 지정

public LinkedList<Double> getCircleArea() { // circleResult getter
        return circleArea;
    }

    public void setCircleArea(LinkedList<Double> circleArea) { // setter
        this.circleArea = circleArea;
    }

private 접근 제어자를 사용했기 때문에 외부에서 circleArea 필드에 접근하게 하기 위해서 getter와 setter 메서드를 활용

    public double calculateCircleArea(int radius) { // 원의 넓이를 구하는 메서드, 매게변수 정수 반지름을 받는다
        return Pi * radius * radius; // 반지름* 반지름 *pi = 원의 넓이
    }
    // 원 넓이 조회
    public void inquiryCircleArea() {
        for (double results : circleArea) {
            System.out.print(results + " ");
        }
        System.out.println();
    }

원의 넓이를 구하는 메서드, 매게변수 정수 반지름(int radius)을 받는다.

App

package calculator;

import java.text.BreakIterator;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Scanner;

public class App {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        Calculator calculator = new Calculator(); // Calculator 클래스의 인스턴스를 생성

        while (true) {

            System.out.print("첫 번째 숫자를 입력하세요: ");
            //Scanner를 사용하여 양의 정수를 입력받고 변수에 저장합니다.
            int firstNumber = sc.nextInt();


            System.out.print("두 번째 숫자를 입력하세요: ");
            int secondNumber = sc.nextInt();


            sc.nextLine(); // nextInt();로 인해 남아있는 것을 없애줌

            System.out.println("계산기 옵션 선택 (번호 입력) : 1. 사칙연산, 2. 원의 넓이 ");
            switch (sc.nextInt()) {
                case 1:
                    try {
                        System.out.print("사칙연산 기호를 입력하세요: "); // +,-,*,/
                        // charAt(idx) : charAt 메서드는 매개변수로 char 타입으로 변환 하고자하는 문자열의 위치를 받는다.
                        char operator = sc.next().charAt(0);
                        int result = calculator.calculate(firstNumber, secondNumber, operator);
                        // calculator.calculate()를 호출하여 계산을 하고 결과를 result에 저장
                        System.out.println("결과: " + result);
                        calculator.getResultList().add(result);

                    } catch (InputErrorException e) { // 잘못된 입력이 발생할시 이를 잡아서 에러 메세지를 출력
                        System.out.println(e.getMessage());

                    }
                    System.out.print("가장 먼저 저장된 연산 결과를 삭제하시겠습니까? (remove 입력 시 삭제)");

                    if (sc.next().equals("remove")) {
                        calculator.removeResultList(); //"remove" 입력시 가장 첫번째 입력값 제거
                        // calculator 클래스에 공용 메서드를 호출

                    }

                    System.out.print("저장된 연산결과를 조회하시겠습니까? (inquiry 입력 시 조회)");
                    if (sc.next().equals("inquiry")) {
                        calculator.inquiryResultList(); // 결과 조회
                    }
                    sc.nextLine(); //버퍼에 남아있는 개행문자 제거
                    System.out.print("더 계산하시겠습니까? (exit 입력 시 종료)");
                    String exit = sc.nextLine();
                    if (exit.equals("exit")) {
                        break;
                    }
                    // 7. 원의 넓이 구하기
                case 2:
                    System.out.print("원의 반지름 입력 : ");
                    int radius = sc.nextInt(); // 원의 반지름 입력 받음

                    double result = calculator.calculateCircleArea(radius);
                    // 원의 넓이를 구하는 메서드를 호출하고 result에 저장한다

                    System.out.println("결과 : " + result);

                    calculator.getCircleArea().add(result); // 컬렉션에 결과 저장
                    calculator.inquiryCircleArea(); // 원의 넓이 결과 조회

                    sc.nextLine(); //버퍼에 남아있는 개행문자 제거
                    System.out.print("더 계산하시겠습니까? (exit 입력 시 종료)");
                    if (sc.nextLine().equals("exit")) {
                        break;

                    }

            }
        }

    }


}

해설

원의 넓이를 구하는 경우를 만들어 줘야 하므로 switch문을 활용하여 case 1에는 사칙연산을 구하는 경우 case 2에는 원의 넓이를 구하는 경우를 만들어 주었다.

git hub
https://github.com/Sangmin1999/calculator/commit/913c2e09d5d3519ba1e4ed8d52252aaebf5c6d8c

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

Calculator

public abstract class Calculator {// 연산 결과를 저장하고 있는 컬렉션 필드에 직접 접근하지 못하도록 수정
    private LinkedList<Double> resultList; // private 접근 제어자 사용
    // 연산 결과를 저장하기 위한 컬렉션 선언 및 생성

해설

새롭게 만들어야 할 ArithmeticCalculator,CircleCalculator 클래스에서 부모 클래스로 상속해야 하므로 Calculator 클라스를 추상 클래스로 바꾸어 주었다.

    // 추상 메서드
    protected abstract double calculate(int firstNumber, int secondNumber, char operator) throws InputErrorException;
    protected abstract double calculate(int radius);

자식 클래스에서 오버라이딩해서 사용하기 위한 사칙연산과 원의 넓이를 구하는 추상메서드를 각각 만들어 주었다.

ArithmeticCalculator

package calculator;

public class ArithmeticCalculator extends Calculator { // Calculator 클래스를 상속
    @Override
    public double calculate(int firstNumber, int secondNumber, char operator) throws InputErrorException {
        int result = 0;
        switch (operator) {
            case '+' : // 사칙 연산을 수행 후 result에 저장
                result = firstNumber + secondNumber;
                break;
            case '-' :
                result = firstNumber - secondNumber;
                break;
            case '*' :
                result = firstNumber * secondNumber;
                break;
            case '/' :
                if (secondNumber == 0)  // 나눗셈 연산에서 두번째 정수에 0이 입력되는 경우 Exception 발생
                    throw new InputErrorException("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다.");

                else
                    result = firstNumber / secondNumber;
                break;
        }
        return result;
    }
    // 사용하지 않는 오버라이딩 메서드를 접근제어자를 protected를 써서 접근 불가능하게 만듬
    @Override
    protected double calculate(int radius) {
        return 0;
    }
}

해설

Calculator 부모 클래스를 상속 받는ArithmeticCalculator 자식 클래스를 생성 후 오버라이딩 메서드를 만들어 주었고 각각 사용하는 메서드는 접근 제어자를 public, 사용하지 않는 메서드는 private을 통해 만들어 주었다.

CircleCalculator

package calculator;

public class CircleCalculator extends Calculator{
    private static final double Pi = 3.14; // 원의 지름 값은 3.14로 변하지 않는 값이기 때문에

    // 사용하지 않는 오버라이딩 메서드를 접근제어자를 protected를 써서 접근 불가능하게 만듬
    @Override
    protected double calculate(int firstNumber, int secondNumber, char operator) throws InputErrorException {
        return 0;
    }


    @Override
    public double calculate(int radius) {
        return Pi* radius *radius;
    }
}

이렇게 원의 넓이를 구하는 Calculator를 상속받는 CircleCalculator 클래스를 만들어 주었다.

App

        // 사칙연산, 원 넓이를 계산하는 두개의 객체를 각각 생성
        ArithmeticCalculator arithmeticCalculator = new ArithmeticCalculator();
        CircleCalculator circleCalculator = new CircleCalculator();

해설

사칙연산, 원 넓이를 계산하는 두개의 객체를 각각 생성해준다.

git hub
https://github.com/Sangmin1999/calculator/commit/65abaed6956928d318465656500d0c11bfdf1444

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

들어가기전 생각

사칙연산을 하는 클래스를 각각 4개를 만들고 사칙연산 클래스 필드를 선언하고 생성해주려고 하였다.

AddOperator

package calculator;

public class AddOperator {
    public int operatre(int firstNumber, int secondNumber) { // 메서드 구현
        return firstNumber + secondNumber;
    }
}

SubtractOperator

package calculator;

public class SubstractOperator {
    public int operatre(int firstNumber, int secondNumber) { // 메서드 구현
        return firstNumber - secondNumber;
    }
}

MultiplyOperator

package calculator;

public class MultiplyOperator {
    public int operatre(int firstNumber, int secondNumber) { // 메서드 구현
        return firstNumber * secondNumber;
    }
}

DivideOperator

package calculator;

public class DivideOperator {
    public int operatre(int firstNumber, int secondNumber) { // 메서드 구현
        return firstNumber / secondNumber;
    }
}

ArithmeticCalculator


public class ArithmeticCalculator extends Calculator { // Calculator 클래스를 상속
    private AddOperator addOperator; // 사칙연산 클래스 필드 선언
    private SubstractOperator substractOperator;
    private MultiplyOperator multiplyOperator;
    private DivideOperator divideOperator;

    public ArithmeticCalculator() { // 부모 생성자를 자동 호출
        addOperator = new AddOperator(); // 사칙연산 클래스 필드 생성
        substractOperator = new SubstractOperator();
       multiplyOperator = new MultiplyOperator();
        divideOperator = new DivideOperator();
    }

해설

각각 만들어준 4개의 클래스를 ArithmeticCalculator 클래스에 필드를 선언해주고 생성해준다.(사칙연산 클래스들을 초기화 해주기 위해서)

git hub
https://github.com/Sangmin1999/calculator/commit/60ba4f4368269063fa2cd9f1aaab7537a431c1ec

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

들어가기전 고민

나머지 연산 기능을 추가하기 위한 ModOperator 클래스를 만들어 주고, 사칙 연산 메서드의 형태가 4클래스 모두 같기 때문에 Operator 추상 클래스를 만들어서 사칙연산 클래스가 오버로딩 할 수 있도록 해주어야 겠다고 생각했다.

ModOperator

package calculator;

public class ModOperator {
    public int operatre(int firstNumber, int secondNumber) { // 나머지 기능 구현
        return firstNumber % secondNumber;
    }
}

Operator

package calculator;

public abstract class Operator { // 오버로딩
    // 사칙 연산 메서드의 형태(이름, 매게변수)가 모두 같기 때문에 추상메서드로 만들었다.
    public abstract int operatre(int firstNumber, int secondNumber);
}

기존의 사칙연산 클래스들을 위의 클래스를 상속 받도록 만들어 주었다

package calculator;

public class AddOperator extends Operator { // Operator 클래스를 상속받아서 오버라이딩(재정의)을 한다
    public int operatre(int firstNumber, int secondNumber) { // 메서드 구현
        return firstNumber + secondNumber;
    }
}
package calculator;

public class SubstractOperator extends Operator {
    public int operatre(int firstNumber, int secondNumber) { // 메서드 구현
        return firstNumber - secondNumber;
    }
}
package calculator;

public class MultiplyOperator extends Operator {
    public int operatre(int firstNumber, int secondNumber) { // 메서드 구현
        return firstNumber * secondNumber;
    }
}
package calculator;

public class DivideOperator extends Operator {
    public int operatre(int firstNumber, int secondNumber) { // 메서드 구현
        return firstNumber / secondNumber;
    }
}
package calculator;

public class ModOperator extends Operator {
    public int operatre(int firstNumber, int secondNumber) { // 나머지 기능 구현
        return firstNumber % secondNumber;
    }
}

그리고 사칙연산 계산을 구현하는 ArithmeticCalculator 클래스에서


@Override
    public int calculate(int firstNumber, int secondNumber, char operator) throws InputErrorException {
        int result = 0;
        switch (operator) {
            case '+' : // 사칙 연산을 수행 후 result에 저장
                result = addOperator.operatre(firstNumber , secondNumber);
                break;
            case '-' :
                result = substractOperator.operatre(firstNumber, secondNumber);
                break;
            case '*' :
                result = multiplyOperator.operatre(firstNumber, secondNumber);
                break;
            case '/' :
                if (secondNumber == 0)  // 나눗셈 연산에서 두번째 정수에 0이 입력되는 경우 Exception 발생
                    throw new InputErrorException("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다.");

                else
                    result = divideOperator.operatre(firstNumber, secondNumber);
                break;
            case '%' :
                result = modOperator.operatre(firstNumber, secondNumber);
                break;
        }
        return result;
    }

operate 메서드를 가져와서 사용했다.

아쉬운 점

이렇게 코드를 변경 후 실행은 잘 되었지만 실행과정에서 case 1 (사칙연산) 부분이 실행된 후 다시 어떤 연산을 할건지 물어보지 않고 바로 원의 넓이를 구하는 부분이 실행되는 문제가 발생하였다. 이를 해결해주기 위해서 기존의 break; 를 쓰는 곳에 대신 return을 활용해 주었다.

   while (true) {

            System.out.println("계산기 옵션 선택 (번호 입력) : 1. 사칙연산, 2. 원의 넓이 ");
            switch (sc.nextInt()) {
                case 1:
                    System.out.print("첫 번째 숫자를 입력하세요: ");
                    //Scanner를 사용하여 양의 정수를 입력받고 변수에 저장합니다.
                    int firstNumber = sc.nextInt();


                    System.out.print("두 번째 숫자를 입력하세요: ");
                    int secondNumber = sc.nextInt();

                    System.out.print("사칙연산 기호를 입력하세요: "); // +,-,*,/
                    // charAt(idx) : charAt 메서드는 매개변수로 char 타입으로 변환 하고자하는 문자열의 위치를 받는다.
                    char operator = sc.next().charAt(0);


                    try {
                        double result = arithmeticCalculator.calculate(firstNumber, secondNumber, operator);
                        // calculator.calculate()를 호출하여 계산을 하고 결과를 result에 저장
                        System.out.println("결과: " + result);
                        arithmeticCalculator.getResultList().add(result);

                    } catch (InputErrorException e) { // 잘못된 입력이 발생할시 이를 잡아서 에러 메세지를 출력
                        System.out.println(e.getMessage());

                    }
                    System.out.print("가장 먼저 저장된 연산 결과를 삭제하시겠습니까? (remove 입력 시 삭제)");

                    if (sc.next().equals("remove")) {
                        arithmeticCalculator.removeResultList(); //"remove" 입력시 가장 첫번째 입력값 제거
                        // calculator 클래스에 공용 메서드를 호출

                    }

                    System.out.print("저장된 연산결과를 조회하시겠습니까? (inquiry 입력 시 조회)");
                    if (sc.next().equals("inquiry")) {
                        arithmeticCalculator.inquiryResultList(); // 결과 조회
                    }
                    sc.nextLine(); //버퍼에 남아있는 개행문자 제거
                    System.out.print("더 계산하시겠습니까? (exit 입력 시 종료)");
                    String exit = sc.nextLine();
                    if (exit.equals("exit")) {
                        return; // case1이 끝나면 case2로 넘어가는 문제를 해결하기 위해서 break -> return으로 교체
                    }
                    break;
                    // 7. 원의 넓이 구하기
                case 2:
                    System.out.print("원의 반지름 입력 : ");
                    int radius = sc.nextInt(); // 원의 반지름 입력 받음

                    double result = circleCalculator.calculate(radius);
                    // 원의 넓이를 구하는 메서드를 호출하고 result에 저장한다

                    System.out.println("결과 : " + result);

                    circleCalculator.getResultList().add(result); // 컬렉션에 결과 저장
                    circleCalculator.inquiryResultList(); // 원의 넓이 결과 조회

                    sc.nextLine(); //버퍼에 남아있는 개행문자 제거
                    System.out.print("더 계산하시겠습니까? (exit 입력 시 종료)");
                    if (sc.nextLine().equals("exit")) {
                        return;// case1이 끝나면 case2로 넘어가는 문제를 해결하기 위해서 break -> return으로 교체

                    }
                    break;

            }

git hub
https://github.com/Sangmin1999/calculator/commit/381b34addff0893516d17a4d9f103a417fe55ed5

인터페이스화

코드를 좀 더 활용해 보고 싶어서 추상메서드를 인터페이스 명으로 지정하고 오버라이딩 후 구현해 보는 과정을 진행해 보았다.

Operator

package calculator;

public interface Operator { // 오버로딩
    // 사칙 연산 메서드의 형태(이름, 매게변수)가 모두 같기 때문에 추상메서드로 만들었다.
      int operatre(int firstNumber, int secondNumber);
}

interface 로 Operator를 인터페이스명으로 지정해 주고

package calculator;

public class AddOperator implements Operator { // Operator 클래스를 상속받아서 오버라이딩(재정의)을 한다
    public int operatre(int firstNumber, int secondNumber) { // 메서드 구현
        return firstNumber + secondNumber;
    }
}

기존의 사칙연산 클래스들을 implements로 상속받도록 하였다.

Warning

위험이 떠서 이유를 찾아보았더니 final를 활용하여 필드가 초기화된 이후에 변경되지 않는 것을 명시적으로 표현해주면서 코드의 명확성과 안전성을 높여주었다.

public class ArithmeticCalculator extends Calculator { // Calculator 클래스를 상속
    // 초기화된 이후에 변경되지 않으므로 (final)
    private final AddOperator addOperator; // 사칙연산 클래스 필드 선언
    private final SubstractOperator substractOperator;
    private final MultiplyOperator multiplyOperator;
    private final DivideOperator divideOperator;
    private final ModOperator modOperator;

최종 코드
git hub : https://github.com/Sangmin1999/calculator/commit/6f6141e7419af0cbebe65513f33238fdf7a365dd

profile
안녕하세요

0개의 댓글