본문 바로가기

카테고리 없음

계산기 3

App 클래스의 main 메서드에서 Calculator 클래스의 연산 결과를 저장하고 있는 컬렉션 필드에 직접 접근하지 못하도록 수정합니다. (캡슐화)
간접 접근을 통해 필드에 접근하여 가져올 수 있도록 구현합니다. (Getter 메서드)
간접 접근을 통해 필드에 접근하여 수정할 수 있도록 구현합니다. (Setter 메서드)
위 요구사항을 모두 구현 했다면 App 클래스의 main 메서드에서 위에서 구현한 메서드를 활용 해봅니다.
public class Calculator {
		/* 연산 결과를 저장하는 컬렉션 타입 필드를 외부에서 직접 접근 하지 못하도록 수정*/
		
    public 반환타입 calculate(...매개변수) {
        ...
    }
    
    /* Getter 메서드 구현 */
    /* Setter 메서드 구현 */
}

public class App {
    public static void main(String[] args) {
        /* Calculator 인스턴스 생성 */

        Scanner sc = new Scanner(System.in);

        /* 반복문 시작 */
            System.out.print("첫 번째 숫자를 입력하세요:");
            int num1 = sc.nextInt();
            System.out.print("두 번째 숫자를 입력하세요:");
            int num2 = sc.nextInt();

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

            /* 위 요구사항에 맞게 소스 코드 수정 */

            System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
            ...
        /* 반복문 종료 */
    }
}

 

계산기 클래스에 private를 넣고

public ArrayList<Integer> getResultList() {//getter 사용
    return new ArrayList<>(resultList);
}

 

이렇게 getter을 넣어서 직접 여기서 결과를 저장하도록 하고

 

앱 클래스에

//        ArrayList<Integer> resultList = new ArrayList<>(); getter 사용으로 계산기와 연결되기에 이젠 이건 사용안함

 

이걸 제거하고

ArrayList<Integer> resultList = calculator.getResultList();//getter로 계산기와 연결

 

이렇게 계산기 클래스에 들어가 간접으로 할수있게 한다.

 

 

다음으론

Calculator 클래스에 저장된 연산 결과들 중  가장 먼저 저장된 데이터를 삭제하는 기능을 가진 메서드를 구현한 후 App 클래스의 main 메서드에 삭제 메서드가 활용될 수 있도록 수정합니다.
public class Calculator {
		/* 연산 결과를 저장하는 컬렉션 타입 필드를 외부에서 직접 접근 하지 못하도록 수정*/
		
    public 반환타입 calculate(...매개변수) {
        ...
    }
    
    ...
    
    public void removeResult() {
        /* 구현 */
    }
}

public class App {
    public static void main(String[] args) {
        /* Calculator 인스턴스 생성 */

        Scanner sc = new Scanner(System.in);

        /* 반복문 시작 */
            System.out.print("첫 번째 숫자를 입력하세요:");
            int num1 = sc.nextInt();
            System.out.print("두 번째 숫자를 입력하세요:");
            int num2 = sc.nextInt();

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

            /* 위 요구사항에 맞게 소스 코드 수정 */

            System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
            ...
        /* 반복문 종료 */
    }
}

이제 제거기능을 계산기에 이관한다.

public void removeResult() {
    if (!resultList.isEmpty()) {
        resultList.remove(0);
    }
}

이걸 넣어주고,

 

앱에는

 if (Objects.equals(text, "remove")) {
                    if (Objects.equals(text, "remove")) {
                        calculator.removeResult();//calculator랑 연결해서 간결해짐
                        System.out.println("첫번째 결과가 삭제되었습니다.");

//                    if (!resultList.isEmpty()) {//결과가 비었는지 확인
//                        resultList.remove(0);//차있으면 첫번째(0)를 삭제
//                        System.out.println("첫번째 결과가 삭제되었습니다.");
//                    } else {
//                        System.out.println("저장된 결과가 없습니다."); 이건 생각해보니 데이터를 지운 상태에선 inquiry를 못하니 필요 없어서 삭제
                    }

 

이렇게 계산기와 연결되서 더 간결해진다.

 

Calculator 클래스에 저장된 연산 결과들을 조회하는 기능을 가진 메서드를 구현한 후 App 클래스의 main 메서드에 조회 메서드가 활용될 수 있도록 수정합니다.
public class Calculator {
		/* 연산 결과를 저장하는 컬렉션 타입 필드를 외부에서 직접 접근 하지 못하도록 수정*/
		
    public 반환타입 calculate(...매개변수) {
        ...
    }
    
    ...

    public void inquiryResults() {
			  /* 구현 */
    }
}

public class App {
    public static void main(String[] args) {
        /* Calculator 인스턴스 생성 */

        Scanner sc = new Scanner(System.in);

        /* 반복문 시작 */
            System.out.print("첫 번째 숫자를 입력하세요:");
            int num1 = sc.nextInt();
            System.out.print("두 번째 숫자를 입력하세요:");
            int num2 = sc.nextInt();

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

            /* 위 요구사항에 맞게 소스 코드 수정 */

            System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
            ...
        /* 반복문 종료 */
    }
}

 

이제 제거뿐 아니라 조회도 집어넣어야 한다.

계산기에

public ArrayList<Integer> inquiryResults() {
    return new ArrayList<>(resultList);
}

 

앱은

if (Objects.equals(text, "inquiry")) {
    resultList = calculator.inquiryResults();
    if (!resultList.isEmpty()) {
        System.out.println("저장된 연산 결과:");
        resultList.forEach(System.out::println);
    } else {
        System.out.println("저장된 결과가 없습니다.");
    }
}

 

그리고 실수로

 

 if (Objects.equals(text, "remove")) {
                    if (Objects.equals(text, "remove")) { 이렇게 중복시켜서

if (Objects.equals(text, "remove")) {
    calculator.removeResult();//calculator랑 연결해서 간결해짐
    System.out.println("첫번째 결과가 삭제되었습니다.");

 

이렇게 돌려놨다.

 

다음 문제는

 

Calculator 인스턴스를 생성(new)할 때 생성자를 통해 연산 결과를 저장하고 있는 컬렉션 필드가 초기화 되도록 수정합니다.
public class Calculator {
		/* 연산 결과를 저장하는 컬렉션 타입 필드가 생성자를 통해 초기화 되도록 변경 */
		/* 생성자 구현 */
		...
}

public class App {
    public static void main(String[] args) {
        /* 위 요구사항에 맞게 Calculator 인스턴스 생성 부분 수정 */

        ...
    }
}

 

 

이건 앱에서

public static void main(String[] args) {
    Calculator calculator = new Calculator();// 앱 실행시 새 계산기 생성

 

이걸 통해 실행시 새 계산기를 만들면

계산기에서

public Calculator() {
    this.resultList = new ArrayList<>();//Calculator calculator = new Calculator();가 실행되면 resultList 필드를 초기화
}

 

이걸로 결과를 초기화한다.

 

 

다음문제는

 

Calculator 클래스에 반지름을 매개변수로 전달받아 원의 넓이를 계산하여 반환해주는 메서드를구현합니다.
APP 클래스의 main 메서드에 Scanner를 활용하여 사칙연산을 진행할지 원의 넓이를 구할지 
명령어를 입력 받은 후 원의 넓이를 구하는 것을 선택했을 때 원의 반지름을 입력 받아 원의 
넓이를 구한 후 출력되도록 구현합니다.

기존에 구현되어있던 사칙연산 기능은 수정 후에도 반드시 이전과 동일하게 동작해야합니다.
이때, static, final 키워드를 활용할 수 있는지 고민한 후 활용 해봅니다.

반드시 static, final 키워드에 대한 설명과 활용한 이유에 대해 주석으로 작성합니다.


원의 넓이 결과를 저장하는 컬렉션 타입의 필드 선언 및 생성
계산된 원의 넓이를 저장합니다.
생성자로 초기화됩니다.
외부에서 직접 접근할 수 없습니다.
Getter, Setter 메서드를 구현합니다.
원의 넓이 결과값들을 조회하는 메서드를 구현합니다.


public class Calculator {
		/* static, final 활용 */
		/* 원의 넓이 결과를 저장하는 컬렉션 타입의 필드 선언 및 생성 */
		/* 생성자 수정 */
		...
		
		/* 원의 넓이를 구하는 메서드 선언*/
		public 반환타입 calculateCircleArea(매배변수) {
        /* 원의 넓이 계산 구현 */
    }
		/* 원의 넓이 저장 필드 Getter, Setter, 조회 메서드 구현 */
}

public class App {
    public static void main(String[] args) {
        /* Calculator 인스턴스 생성 */

        Scanner sc = new Scanner(System.in);

        /* 반복문 시작 */
            /* 사칙연산을 진행할지 원의 너비를 구할지 선택 구현 */
            ...
            /* 원의 넓이를 구하는 경우 반지름을 입력받아 원의 넓이를 구한 후 출력*/
            /* 원의 넓이 저장 */
            /* 저장된 원의 넓이 값들 바로 전체 조회 */
        
		        System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
        /* 반복문 종료 */
    }
}

 

먼저 계산기를

 

import java.util.ArrayList;

public class Calculator {
    private ArrayList<Integer> resultList;// private로 접근 못하게
    private ArrayList<Double> circleAreaList;//private로 접근 못하게

    public static final double PI = Math.PI;// tatic, final 키워드에 대한 설명과 활용한 이유에 대해 주석으로 작성합니다.라고 해서
    // 원주율은 절대 변하지 않는 상수이므로 final static으로 작성

    public Calculator() {
        this.resultList = new ArrayList<>();//Calculator calculator = new Calculator();가 실행되면 resultList 필드를 초기화

        // 원의 넓이 결과를 저장하는 ArrayList를 만들고 그것도 같이 초기화 시킨다
        this.circleAreaList = new ArrayList<>();
    }

    public int calculate(int num1, int num2, char operator) throws IllegalArgumentException {
        int result;
        if (operator == '+') {
            result = num1 + num2;
        } else if (operator == '-') {
            result = num1 - num2;
        } else if (operator == '*') {
            result = num1 * num2;
        } else if (operator == '/') {
            if (num2 == 0) {
                throw new IllegalArgumentException("0으로 나눌 수 없습니다.");
            }
            result = num1 / num2;
        } else {
            throw new IllegalArgumentException("잘못된 연산자입니다.");
        }
        resultList.add(result);
        return result;
    }

    public ArrayList<Integer> getResultList() {//getter 사용
        return new ArrayList<>(resultList);
    }//getter사용
    public void removeResult() {
        if (!resultList.isEmpty()) {
            resultList.remove(0);
        }
    }// 맨 앞에꺼 제거기능

    public ArrayList<Integer> inquiryResults() {
        return new ArrayList<>(resultList);

    }//결과를 계산기에 저장


    public double calculateCircleArea(double radius) {
        double area = PI * radius * radius;
        circleAreaList.add(area);
        return area;
    }//원의 넓이 계산

    public ArrayList<Double> getCircleAreaList() {//getter사용
        return new ArrayList<>(circleAreaList);
    } // 원의 넓이 결과를 가져옴
}


 

하나씩 보면,

private ArrayList<Double> circleAreaList;//private로 접근 못하게

 

이걸로 외부에서 접근 못하게 막고

public static final double PI = Math.PI;// static, final 키워드에 대한 설명과 활용한 이유에 대해 주석으로 작성합니다.라고 해서
    // 원주율은 절대 변하지 않는 상수이므로 final, static 은 여기의 모든 클래스가 원주율을 그대로 공유하니까

 

final, static를 쓰라고 한걸 쓰는데, 이유는 원주율은 절대불변이므로 final을 쓰는게 맞고 대문자로, static은 그 원주율을 공유해야 하니까

 

public double calculateCircleArea(double radius) {
        double area = PI * radius * radius;//원의 반지름을 매개변수로 받아 원의 넓이를 계산
        circleAreaList.add(area);//원의 넓이를 저장
        return area;
    }

    public ArrayList<Double> getCircleAreaList() {//getter사용
        return new ArrayList<>(circleAreaList);
    } // 위에서 저장된 원의 넓이 결과를 가져옴

 

이것이 원의 넓이를 구하고, 그 결과를  저장한뒤 반환하는걸 추가한다.

 

그리고 앱은

import java.util.ArrayList;
import java.util.InputMismatchException;
import java.util.Objects;
import java.util.Scanner;

public class App {
    public static void main(String[] args) {
        Calculator calculator = new Calculator(); // 앱 실행시 새 계산기 생성
        Scanner sc = new Scanner(System.in);

        // jcf는 list, set, map이 있는데 list 사용
        // ArrayList<Integer> resultList = new ArrayList<>(); // getter 사용으로 계산기와 연결되기에 이젠 이건 사용안함

        while (true) { // 반복을 시작하기위해 추가
            System.out.println("원하는 연산을 선택하세요: 1. 사칙연산, 2. 원의 넓이 계산");
            int choice = sc.nextInt();

            try { // 틀려도 종료되지 않기 위해 이거 전체를 try catch로 감싸기 그래서 하나씩 감싼것도 없앴다.
                if (choice == 1) {//사칙연산을 할지 원의 넓이를 정할지를 선택 가능한 숫자 입력 방식
                    System.out.print("첫 번째 숫자를 입력하세요: ");
                    int num1 = sc.nextInt();

                    System.out.print("사칙연산 기호를 입력하세요: ");
                    char operator = sc.next().charAt(0); // 사칙연산은 한글자니 char, 연산자는 operator를 써야한다.

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

                    int result = calculator.calculate(num1, num2, operator); // 계산기에 접속해 계산
                    System.out.println("결과: " + result);
                    ArrayList<Integer> resultList = calculator.getResultList(); // getter로 계산기와 연결
                    System.out.println("현재 저장된 결과의 수: " + resultList.size());

                    System.out.println("가장 먼저 저장된 연산 결과를 삭제하시겠습니까? 아니면 결과들을 보시겠습니까? (remove 입력 시 삭제, inquiry 입력시 결과 출력)");
                    String text = sc.next(); // 위에 안내문 수정
                    if (Objects.equals(text, "remove")) { // remove 중복 수정
                        calculator.removeResult(); // calculator랑 연결해서 간결해짐
                        System.out.println("첫번째 결과가 삭제되었습니다.");


                    } else if (Objects.equals(text, "inquiry")) {
                        resultList = calculator.inquiryResults();//마찬가지로 calculator랑 연결해서 간결해짐
                        if (!resultList.isEmpty()) {
                            System.out.println("저장된 연산 결과:");
                            resultList.forEach(System.out::println);
                        } else {
                            System.out.println("저장된 결과가 없습니다.");
                        }
                    }
                } else if (choice == 2) {
                    System.out.print("원의 반지름을 입력하세요: ");
                    double radius = sc.nextDouble();
                    double area = calculator.calculateCircleArea(radius);
                    System.out.println("계산된 원의 넓이: " + area);

                    ArrayList<Double> circleAreaList = calculator.getCircleAreaList();
                    System.out.println("저장된 원의 넓이 값들: " + circleAreaList);//이걸로 inquiry없이 바로 전체 조회 가능
                }

                System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
                String text = sc.next();
                if (Objects.equals(text, "exit")) {
                    break;
                }
            } catch(InputMismatchException e){
                System.out.println("잘못된 입력입니다. 숫자를 입력하거나 올바른 명령어를 입력해주세요.");
                sc.next(); // 이걸로 틀린거 입력시에도 다시 입력가능
            }
        }
    }
}


 

 

if (choice == 1) {//사칙연산을 할지 원의 넓이를 정할지를 선택 가능한 숫자 입력 방식

 

이걸로 1번은 사칙연산 2번은 원 넓이로 정하고 

else if (choice == 2) {
    System.out.print("원의 반지름을 입력하세요: ");
    double radius = sc.nextDouble();//입력된 반지름을 radius에 저장
    double area = calculator.calculateCircleArea(radius);//calculateCircleArea(radius) 메서드를 호출하여 원의 넓이를 계산하고,
                    // 그 결과를 area라는 변수에 저장
    System.out.println("계산된 원의 넓이: " + area);//area를 호출

    ArrayList<Double> circleAreaList = calculator.getCircleAreaList();//getter을 사용해서 넓이가 저장된 리스트를 가져옴
    System.out.println("저장된 원의 넓이 값들: " + circleAreaList);//이걸로 inquiry없이 바로 전체 조회 가능
}

 

 

이걸로 반지름을 입력받으면 계산기에 접속해서 그 결과를 저장하고, 저장된값들을 바로 출력해서 보여줄수가 있다.

 

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

  • 기존에 만들어둔 Calculator 클래스를 수정합니다
  • 수정한 Calculator 클래스를 활용하여 ArithmeticCalculator, CircleCalculator 클래스를 구현 해봅니다. (상속)
  • 위 요구사항을 구현하게되면 App 클래스의 main 메서드에 오류가 발생할 겁니다.
    • 구현한 클래스들을 활용하여 오류가 발생하지 않고 활용될 수 있도록 수정 해보세요!
    • 기존에 사칙연산을 저장하던 컬렉션 필드의 타입을 Double로 변경해도 괜찮습니다.
    • 필드의 접근 제어자를 변경해도 괜찮습니다.

 

이걸 수행하기 위해

public class ArithmeticCalculator extends Calculator {//이쪽이 이제 사칙연산 담당
    public double calculate(int num1, int num2, char operator) throws IllegalArgumentException {
        double result;
        if (operator == '+') {
            result = num1 + num2;
        } else if (operator == '-') {
            result = num1 - num2;
        } else if (operator == '*') {
            result = num1 * num2;
        } else if (operator == '/') {
            if (num2 == 0) {
                throw new IllegalArgumentException("0으로 나눌 수 없습니다.");
            }
            result = (double) num1 / num2;
        } else {
            throw new IllegalArgumentException("잘못된 연산자입니다.");
        }
        addResult(result);
        return result;
    }
}

 

이게 이제 사칙연산을 대신 수행하고 Double을 써도 된다고 했기에 바꿔준다.

 

public class CircleCalculator extends Calculator { //이쪽이 반지름 담당
    public static final double PI = Math.PI;// static, final 키워드에 대한 설명과 활용한 이유에 대해 주석으로 작성합니다.라고 해서
//    // 원주율은 절대 변하지 않는 상수이므로 final, static 은 여기의 모든 클래스가 원주율을 그대로 공유하니까

    public double calculateCircleArea(double radius) {
        double area = PI * radius * radius;//원의 반지름을 매개변수로 받아 원의 넓이를 계산
        addResult(area);//원의 넓이를 저장
        return area;
    }
}

 

이게 원의 넓이를 구할수있다.

 

대신 계산기는

public class Calculator {
    
    protected ArrayList<Double> resultList;//상속용

    public Calculator() {
        this.resultList = new ArrayList<>();//새로 초기화 하는 기능
    }

    public void addResult(Double result) {
        resultList.add(result);//계산결과 저장
    }

    public ArrayList<Double> getResultList() {//결과를 조회하는 기능
        return new ArrayList<>(resultList);
    }
    protected void removeResult() {// 앞에 있는걸 삭제하는기능
        if (!resultList.isEmpty()) {
            resultList.remove(0);
        }
    }
}

 

이렇게되어 가벼워진다. 그리고 protected 를 써서 자식들이 상속받게끔 해준다.

 

앱의 경우에도

 

import java.util.ArrayList;
import java.util.InputMismatchException;
import java.util.Objects;
import java.util.Scanner;

public class App {
    public static void main(String[] args) {
        Calculator calculator = new Calculator(); // 앱 실행시 새 계산기 생성

        //계산기가 아니라 그 자식들과 연결
        ArithmeticCalculator ac = new ArithmeticCalculator();
        CircleCalculator cc = new CircleCalculator();

        Scanner sc = new Scanner(System.in);

        // jcf는 list, set, map이 있는데 list 사용
        // ArrayList<Integer> resultList = new ArrayList<>(); // getter 사용으로 계산기와 연결되기에 이젠 이건 사용안함

        while (true) { // 반복을 시작하기위해 추가
            System.out.println("원하는 연산을 선택하세요: 1. 사칙연산, 2. 원의 넓이 계산");
            int choice = sc.nextInt();

            try { // 틀려도 종료되지 않기 위해 이거 전체를 try catch로 감싸기 그래서 하나씩 감싼것도 없앴다.
                if (choice == 1) {//사칙연산을 할지 원의 넓이를 정할지를 선택 가능한 숫자 입력 방식
                    System.out.print("첫 번째 숫자를 입력하세요: ");
                    int num1 = sc.nextInt();

                    System.out.print("사칙연산 기호를 입력하세요: ");
                    char operator = sc.next().charAt(0); // 사칙연산은 한글자니 char, 연산자는 operator를 써야한다.

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

//                    int result = calculator.calculate(num1, num2, operator); // 계산기에 접속해 계산
                    double result = ac.calculate(num1, num2, operator);//더블로 바꾸고 계산기 대신 ac에 연결
                    System.out.println("결과: " + result);
                    ArrayList<Double> resultList = ac.getResultList(); // getter로 계산기와 연결
                    System.out.println("현재 저장된 결과의 수: " + resultList.size());

                    System.out.println("가장 먼저 저장된 연산 결과를 삭제하시겠습니까? 아니면 결과들을 보시겠습니까? (remove 입력 시 삭제, inquiry 입력시 결과 출력)");
                    String text = sc.next(); // 위에 안내문 수정


                    if (Objects.equals(text, "remove")) { // remove 중복 수정
                        ac.removeResult();// 계산기 대신 ac에 연결
//                        calculator.removeResult(); // calculator랑 연결해서 간결해짐
                        System.out.println("첫번째 결과가 삭제되었습니다.");


                    } else if (Objects.equals(text, "inquiry")) {
                        resultList = ac.getResultList();//계산기 대신 ac에 연결
//                        resultList = calculator.inquiryResults();//마찬가지로 calculator랑 연결해서 간결해짐
                        if (!resultList.isEmpty()) {
                            System.out.println("저장된 연산 결과:");
                            resultList.forEach(System.out::println);
                        } else {
                            System.out.println("저장된 결과가 없습니다.");
                        }
                    }
                } else if (choice == 2) {
                    System.out.print("원의 반지름을 입력하세요: ");
                    double radius = sc.nextDouble();//입력된 반지름을 radius에 저장
                    double area = cc.calculateCircleArea(radius);//계산기 대신 cc에 연결

//                    double area = calculator.calculateCircleArea(radius);//calculateCircleArea(radius) 메서드를 호출하여 원의 넓이를 계산하고,
                    // 그 결과를 area라는 변수에 저장
                    System.out.println("계산된 원의 넓이: " + area);//area를 호출

                    ArrayList<Double> circleAreaList = cc.getResultList();//계산기 대신 cc에 연결

//                    ArrayList<Double> circleAreaList = calculator.getCircleAreaList();//getter을 사용해서 넓이가 저장된 리스트를 가져옴
                    System.out.println("저장된 원의 넓이 값들: " + circleAreaList);//이걸로 inquiry없이 바로 전체 조회 가능
                }

                System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
                String text = sc.next();
                if (Objects.equals(text, "exit")) {
                    break;
                }
            } catch(InputMismatchException e){
                System.out.println("잘못된 입력입니다. 숫자를 입력하거나 올바른 명령어를 입력해주세요.");
                sc.next(); // 이걸로 틀린거 입력시에도 다시 입력가능
            }
        }
    }
}

 

이렇게 calculator과 연결된 부분을 전부 자식으로 바꿨다. 사칙연산은 ac, 원은 cc로 바꾸게 됬다.

 

 

다음 과제는

 

 

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

  • Calculator 클래스에 사칙연산 클래스들을 어떻게 활용할 수 있을지 고민 해봅니다. (포함 관계)
  • 활용 방법을 찾아 적용했을 때 사칙연산 클래스들을 초기화 해야하는데 이때, 반드시 생성자를 활용해 봅니다.
  • 마찬가지로 ArithmeticCalculator 클래스의 연산 메서드를 수정 하더라도 이전과 똑같이 동작해야합니다.

 

 

 

 

 

 

class AddOperator {
public double operate(int num1, int num2) {
return num1 + num2;
}
}

class SubtractOperator {
public double operate(int num1, int num2) {
return num1 - num2;
}
}

class MultiplyOperator {
public double operate(int num1, int num2) {
return num1 * num2;
}
}

class DivideOperator {
public double operate(int num1, int num2) throws IllegalArgumentException {
if (num2 == 0) {
throw new IllegalArgumentException("0으로 나눌 수 없습니다.");
}
return (double) num1 / num2;
}
}

 

그래서 이렇게 만들어주고

 

ArithmeticCalculator에다가

 

public class ArithmeticCalculator extends Calculator {
    //private을 통해 새로 추가한 개체들이 ArithmeticCalculator에만 접근 가능하게 하고
    private AddOperator addOperator;
    private SubtractOperator subtractOperator;
    private MultiplyOperator multiplyOperator;
    private DivideOperator divideOperator;

    public ArithmeticCalculator() {
        this.addOperator = new AddOperator();
        this.subtractOperator = new SubtractOperator();
        this.multiplyOperator = new MultiplyOperator();
        this.divideOperator = new DivideOperator();
    }//이제 ArithmeticCalculator 객체가 생성될 때마다 각 연산 클래스의 인스턴스도 함께 생성되어 초기화되도록 엮어놓는다.

    //원래는 if (operator == '+') {
    //            result = num1 + num2; 이런식으로 직접 계산했지만 이젠  result = addOperator.operate(num1, num2);이렇게 연결만함
    public double calculate(int num1, int num2, char operator) throws IllegalArgumentException {
        double result;
        if (operator == '+') {
            result = addOperator.operate(num1, num2);
        } else if (operator == '-') {
            result = subtractOperator.operate(num1, num2);
        } else if (operator == '*') {
            result = multiplyOperator.operate(num1, num2);
        } else if (operator == '/') {
            result = divideOperator.operate(num1, num2);
        } else {
            throw new IllegalArgumentException("잘못된 연산자입니다.");
        }
        addResult(result);
        return result;
    }

}

 

이렇게 this로 연결되어 초기화와 생성을 같이하고 계산도 각각의 클래스로 넘겨버렸다.

 

 

마지막으로

 

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

  • 추가하려고 하니 앞으로 계속 기능이 추가되면 여러 부분의 소스코드를 수정해야 한다는 생각이 들었고 “현재 비효율적인 구조가 아닌가?” 라는 의구심이 들었습니다.
    • 따라서 소스 코드의 변경은 최소화하면서 기능을 쉽게 추가(확장)할 수 있는 방법을 고민 해봅니다. (OCP)
  • 방법을 고민 및 학습하여 적용했을 때 전체적인 소스 코드와 구조의 변경이 발생 했을 겁니다.
    • 최대한 생각한 방법으로 구현 해보세요. 틀린 답은 없습니다. 컴파일에 문제가 없고 기능이 정상적으로 동작 하면 모두 정답입니다.
    • 포기하는 것 보다 본인이 생각한데로 구현해보고 다른 개발자들과 공유 하면서 여러 가지 방법들을 확인 했을 때 실력이 가장 많이 향상됩니다.
  • 마찬가지로 수정 후에도 이전과 똑같이 동작해야합니다.

 

먼저,

public interface Operator {
    double operate(int num1, int num2);
}

 

이렇게 보조용 인터페이스를 만든뒤

public class AddOperator implements Operator {
    public double operate(int num1, int num2) {
        return num1 + num2;
    }
}

 

이런식으로 implements를 통해 인터페이스와 연결, 이걸 다른 클래스에도 전부 해준다.

그리고 나누기 용으로

public class ModOperator implements Operator {
    public double operate(int num1, int num2) {
        if (num2 == 0) {
            throw new IllegalArgumentException("0으로 나눌 수 없습니다.");
        }
        return num1 % num2;
    }
}

 

이걸 작성한뒤

import java.util.HashMap;
import java.util.Map;

public class ArithmeticCalculator extends Calculator {
    private final Map<Character, Operator> operators;//final 키워드로 선언되어 있어 operators는
    //변하지 않음
    //연산자와 해당 연산을 수행하는 Operator를 매핑하는데 이제 새 연산을 추가할때, 해당 
    //클래스를 만들고 Operator랑 연결하면 되서
    //기능을 쉽게 추가할 수 있으며, 소스 코드의 변경은 최소화되는데 그게 OCP

    public ArithmeticCalculator() {
        this.operators = new HashMap<>();
        this.operators.put('+', new AddOperator());
        this.operators.put('-', new SubtractOperator());
        this.operators.put('*', new MultiplyOperator());
        this.operators.put('/', new DivideOperator());
        this.operators.put('%', new ModOperator());
    }//this를 통해서 operator와 각 클래스랑 연결되고 HashMap이 키와 값으로 이뤄어진걸 연산자를 
    //키로 해서 -를 입력했을때
    //SubtractOperator와 연결되어 두 값을 -하는식으로 쓰인다

    public double calculate(int num1, int num2, char operator) throws IllegalArgumentException {
        Operator op = operators.get(operator);
        if (op == null) {
            throw new IllegalArgumentException("잘못된 연산자입니다.");
        }
        double result = op.operate(num1, num2);
        addResult(result);
        return result;// 입력이 들어오면 맵에서 입력된 연산자에 해당하는 객체를 찿고 연산자가 
        //없다면, 즉 op가 null이라면
        //예외를, 있으면 연산을 수행하고, 그 결과를 result에 저장하고 addResult(result)를 
        //호출하여 결과를 resultList에 추가
    }
}

 

map을 통해 인터페이그와 연결되어 ocp를 달성한다.

 

그리고 this를 통해서 operator와 각 클래스랑 연결되고 HashMap이 키와 값으로 이뤄어진걸 연산자를 키로 해서 -를 입력했을때 SubtractOperator와 연결되어 두 값을 -하는식으로 쓰인다