{"id":15581150,"url":"https://github.com/noxknow/dev_practice","last_synced_at":"2025-03-29T08:22:34.652Z","repository":{"id":193281814,"uuid":"688485122","full_name":"noxknow/Dev_practice","owner":"noxknow","description":"계산기 구현을 통한 객체지향 공부","archived":false,"fork":false,"pushed_at":"2023-09-27T15:03:08.000Z","size":131,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-03T22:39:07.273Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/noxknow.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-09-07T12:50:25.000Z","updated_at":"2023-09-09T09:23:39.000Z","dependencies_parsed_at":null,"dependency_job_id":"4e27c26c-f709-4aab-a4ca-a1ce15daebce","html_url":"https://github.com/noxknow/Dev_practice","commit_stats":null,"previous_names":["noxknow/dev_practice"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noxknow%2FDev_practice","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noxknow%2FDev_practice/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noxknow%2FDev_practice/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noxknow%2FDev_practice/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/noxknow","download_url":"https://codeload.github.com/noxknow/Dev_practice/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246157662,"owners_count":20732655,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-02T19:41:39.941Z","updated_at":"2025-03-29T08:22:34.629Z","avatar_url":"https://github.com/noxknow.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"## 목차\n\n- [1. 문제](#문제) \u003cbr/\u003e\n  - [1.1 요구사항](#요구사항) \u003cbr/\u003e\n  - [1.2 실행결과(콘솔)](#실행결과콘솔) \u003cbr/\u003e\n- [2. 문제 해결 과정]() \u003cbr/\u003e\n  - [2.1 클래스 다이어그램](#-클래스-다이어그램) \u003cbr/\u003e\n  - [2.2 Input과 Output을 Interface로 구현하는 이유는?](#-input과-output을-interface로-구현하는-이유는) \u003cbr/\u003e\n  - [2.3 Enum의 if와 switch](#-enum의-if와-switch) \u003cbr/\u003e\n  - [2.4 BiFunction 인터페이스](#-bifunction-인터페이스) \u003cbr/\u003e\n    - [2.4.1 BiFunction 인터페이스의 apply 메서드](#bifunction-인터페이스의-apply-메서드) \u003cbr/\u003e\n    - [2.4.2 BiFunction 인터페이스의 andThen 메서드](#bifunction-인터페이스의-andthen-메서드) \u003cbr/\u003e\n  - [2.5 Arrays.asList](#-arraysaslist) \u003cbr/\u003e\n  - [2.6 주의할 점](#-주의할-점) \u003cbr/\u003e\n\n# 문제\n자바 계산기 구현.\n\n### 요구사항\n- 콘솔로 구현입니다.(스윙으로 구현하시는 분들 계실까봐) \n- 객체지향적인 코드로 계산기 구현하기\n    - [ ]  더하기\n    - [ ]  빼기\n    - [ ]  곱하기\n    - [ ]  나누기\n    - [ ]  우선순위(사칙연산)\n- [ ]  테스트 코드 구현하기\n- [ ]  계산 이력을 맵으로 데이터 저장기능 만들기\n    - 애플리케이션이 동작하는 동안 데이터베이스 외에 데이터를 저장할 수 있는 방법을 고안해보세요.\n- (선택) 정규식 사용\n\n### 실행결과(콘솔)\n```\n1. 조회\n2. 계산\n\n선택 : 2\n\n1 + 2\n3\n\n1. 조회\n2. 계산\n\n선택 : 2\n\n1 + 2 * 3\n7\n\n1. 조회\n2. 계산\n\n선택 : 1\n\n1 + 2 = 3\n1 + 2 * 3 = 7\n\n선택 : 2\n\n3 - 2 * 2\n-1\n```\n\n# 📑 문제 해결 과정\n\nhttps://github.com/noxknow/Dev_practice/assets/122594223/53705a60-43d8-4f76-ab5b-50277b8843c0\n\n## 🔹 클래스 다이어그램\n\n```mermaid\nclassDiagram\n  class Main {\n    +main(String[] args)\n  }\n\n  class Calculator {\n    -input: Input\n    -output: Output\n    -validate: Validate\n    -compute: Compute\n    -repository: Repository\n    +Calculator(Input, Output, Validate, Compute, Repository)\n    +run(): void\n    +printMenu(): void\n    +getInput(): String\n    +validateInput(String): boolean\n    +selectMenu(String): Menu\n    +runByMenu(ComputeManager, Menu): void\n  }\n\n  class ComputeManager {\n    -state: boolean\n    +ComputeManager(boolean)\n    +getState(): boolean\n    +setState(boolean): void\n  }\n\n  class Menu {\n    \u003c\u003cenumeration\u003e\u003e\n    -number: int\n    +getNumber(): int\n    +getMenu(int): Menu\n  }\n\n  class Validate {\n    \u003c\u003cinterface\u003e\u003e\n    +isValidMenu(String): boolean\n    +isValidExpression(String): boolean\n  }\n\n  class ValidateExpression {\n    -expressionPattern: String\n    -menuPattern: String\n    +isValidMenu(String): boolean\n    +isValidExpression(String): boolean\n  }\n\n  class Input {\n    \u003c\u003cinterface\u003e\u003e\n    +selectMenu(String): String\n    +input(): String\n  }\n\n  class Output {\n    \u003c\u003cinterface\u003e\u003e\n    +printMenu(String): void\n    +printCalculateResult(long): void\n    +printSavedResults(Repository): void\n  }\n\n  class ConsoleInput {\n    -br: BufferedReader\n    +selectMenu(String): String\n    +input(): String\n  }\n\n  class ConsoleOutput {\n    +printMenu(String): void\n    +printCalculateResult(long): void\n    +printSavedResults(Repository): void\n  }\n\n  class Repository {\n    \u003c\u003cinterface\u003e\u003e\n    +saveResult(String, long): void\n    +showSavedResults(): void\n  }\n\n  class ArrayListRepository {\n    -results: List\u003cString\u003e\n    +saveResult(String, long): void\n    +showSavedResults(): void\n  }\n\n  class Compute {\n    \u003c\u003cinterface\u003e\u003e\n    +convertToToken(String): List\u003cString\u003e\n    +convertTokenToPostfix(List\u003cString\u003e): List\u003cString\u003e\n    +calculate(List\u003cString\u003e): long\n    +compute(String): long\n  }\n\n  class ComputeOperation {\n    +convertToToken(String): List\u003cString\u003e\n    +convertTokenToPostfix(List\u003cString\u003e): List\u003cString\u003e\n    +calculate(List\u003cString\u003e): long\n    +compute(String): long\n    -isLowerThanStack(String, String): boolean\n  }\n\n  class Operator {\n    \u003c\u003cenumeration\u003e\u003e\n    +getOperator(String): Operator\n    +calculate(long, long): long\n    +getPriority(): int\n  }\n\n  Main --\u003e Calculator\n  Calculator --\u003e Input\n  Calculator --\u003e Output\n  Calculator --\u003e Validate\n  Calculator --\u003e Compute\n  Calculator --\u003e Repository\n  Calculator --\u003e ComputeManager\n  Calculator --\u003e Menu\n  Validate \u003c-- ValidateExpression\n  Output \u003c-- ConsoleOutput\n  Input \u003c-- ConsoleInput\n  Repository \u003c-- ArrayListRepository\n  Compute \u003c-- ComputeOperation\n  Compute \u003c-- Operator\n```\n\n## 🤔 Input과 Output을 Interface로 구현하는 이유는?\n\n인터페이스를 사용하는 주요 이유는 추상화, 분리, 유연성, 테스트 용이성, 의존성 관리, 코드 재사용성을 향상시키기 위함. 따라서 인터페이스를 통해 Input과 Output을 정의하고 구현하는 것은 소프트웨어의 품질과 유지보수성을 향상시키는 데 도움이 된다.\n\n**단위 테스트 용이성** : 인터페이스를 사용하면 단위 테스트를 쉽게 수행할 수 있다. Mock 객체를 사용하여 인터페이스를 구현하는 클래스의 동작을 테스트할 때 테스트 더블로 인터페이스의 메서드를 구현한 클래스를 대체할 수 있다.\n\n**의존성 관리** : 인터페이스를 사용하면 클래스 간의 결합도를 낮출 수 있다. 즉, 클래스가 직접적으로 다른 클래스에 의존하지 않고 인터페이스에 의존함으로써 코드를 더 유연하게 관리할 수 있다.\n\n## 🔹 Enum의 if와 switch\n\n\u003e **`switch`** 문은 열거형(enum) 타입의 상수를 직접 사용할 수 있지만, **`if`** 문을 사용할 때는 해당 열거형 상수를 직접 비교할 수 없다.\n\u003e\n\n**`switch`** 문에서 열거형(enum) 상수를 사용할 수 있는 이유는 `switch` 문이 내부적으로 열거형 상수의 정수 값(ordinal)을 비교하기 때문이다. 각 열거형 상수는 정수 값으로 매핑되어 있으며, **`switch`** 문은 이 값을 이용하여 분기를 수행한다. 그래서 **`switch`** 문에서는 열거형 상수의 이름을 사용하여 해당 상수를 가져올 수 있다.\n\n하지만 **`if`** 문에서는 직접 열거형 상수의 이름을 비교할 수 없다. `if (selectedMenu == HISTORY)`와 같은 비교는 컴파일 오류를 발생시킨다. 이런 경우에는 열거형 상수를 가져오기 위해 `Menu.HISTORY`와 같이 열거형의 이름과 상수 이름을 사용해야 한다.\n\n따라서 **`if`** 문에서 `Menu.HISTORY`와 같이 열거형 상수를 가져오려면 해당 열거형을 import해야 한다. 이는 열거형 상수를 사용하기 위해 필요한 Java의 문법 규칙이다.\n\n## 🔹 **BiFunction 인터페이스**\n\nBiFunction Interface는 Java에서 함수형 프로그래밍을 구현하기 위해 Java 버전 1.8부터 도입된 함수형 인터페이스로 두 개의 매개변수를 전달받아 특정 작업을 수행 후 새로운 값을 반환하는 경우 사용된다.\n\n제네릭 타입인 두 개의 인수가 존재하며, 제네릭 타입을 반환한다.\n\n```java\n@FunctionalInterface\npublic interface BiFunction\u003cT, U, R\u003e {\n  R apply(T t, U u);\n\n  default \u003cV\u003e BiFunction\u003cT, U, V\u003e andThen(Function\u003c? super R, ? extends V\u003e after) {\n    Objects.requireNonNull(after);\n    return (T t, U u) -\u003e after.apply(apply(t, u));\n  }\n}\n```\n\nBiFunction 인터페이스는 세 개의 제네릭 타입을 사용한다.\n\nT: 첫 번째 매개변수의 타입.\n\nU: 두 번째 매개변수의 타입.\n\nR: 반환 타입.\n\n인터페이스 내부에는 추상 메서드 apply()와 디폴트 메서드인 andThen() 메서드가 존재한다.\n\n람다 표현식을 사용하면 BiFunction 인터페이스의 추상 메서드를 구현하는 클래스를 정의할 필요가 없으며, BiFunction 타입의 객체에 할당된 람다 표현식은 apply() 메서드를 구현하기 위해 사용된다.\n\n### **BiFunction 인터페이스의 apply 메서드**\n\napply() 메서드는 제네릭 타입인 두 개의 매개변수를 전달받아 특정 작업을 수행 후 값을 반환한다.\n\n```java\nR apply(T t, U u);\n```\n\n다음 예제는 apply() 메서드 사용 방법이다.\n\n먼저, 세 개의 BiFunction 타입의 객체를 생성하며 특정 로직을 수행하는 람다 표현식을 할당한다.\n\n- biFunctionAdd 객체는 Integer 타입의 두 개의 매개변수를 전달받아 더한 결과를 문자열로 반환합니다.\n- biFunctionMinus 객체는 Integer 타입의 두 개의 매개변수를 전달받아 뺄셈 결과를 문자열로 반환합니다.\n- biFunctionMultiple 객체는 Integer 타입의 두 개의 매개변수를 전달받아 곱한 결과를 문자열로 반환합니다.\n\n```java\npublic static void main(String args[]) {\n  BiFunction\u003cInteger, Integer, String\u003e biFunctionAdd =\n          (num1, num2) -\u003e  Integer.toString(num1 + num2);\n\n  BiFunction\u003cInteger, Integer, String\u003e biFunctionMinus =\n          (num1, num2) -\u003e Integer.toString(num1 - num2);\n\n  BiFunction\u003cInteger, Integer, String\u003e biFunctionMultiple =\n          (num1, num2) -\u003e Integer.toString(num1 * num2);\n\n  System.out.println(\"100 + 50 = \" + biFunctionAdd.apply(100, 50));\n  System.out.println(\"100 - 50 = \" + biFunctionMinus.apply(100, 50));\n  System.out.println(\"100 * 50 = \" + biFunctionMultiple.apply(100, 50));\n}\n```\n\n[실행 결과]\n\n```html\n100 + 50 = 150\n100 - 50 = 50\n100 * 50 = 5000\n```\n\n---\n\n### **BiFunction 인터페이스의 andThen 메서드**\n\napply() 메서드 실행 후 반환 결과에 대해 특정 작업이 필요한 경우 해당 로직을 andThen() 메서드에 전달한다.\n\n매개변수로 전달되는 함수는 BiFunction 타입이 아니라 Function 타입이어야 한다.\n\n다음 예제는 apply() 메서드 반환 결과를 andThen() 메서드를 사용하여 문자열을 병합한다.\n\n```java\npublic static void main(String args[]) {\n  BiFunction\u003cInteger, Integer, String\u003e biFunctionAdd =\n          (num1, num2) -\u003e  Integer.toString(num1 + num2);\n  biFunctionAdd = biFunctionAdd.andThen(result -\u003e \"biFunctionAdd Result: \" + result);\n\n  BiFunction\u003cInteger, Integer, String\u003e biFunctionMinus =\n          (num1, num2) -\u003e Integer.toString(num1 - num2);\n  biFunctionMinus = biFunctionMinus.andThen(result -\u003e \"biFunctionMinus Result: \" + result);\n\n  BiFunction\u003cInteger, Integer, String\u003e biFunctionMultiple =\n          (num1, num2) -\u003e Integer.toString(num1 * num2);\n  biFunctionMultiple = biFunctionMultiple.andThen(result -\u003e \"biFunctionMultiple Result: \" + result);\n\n  System.out.println(biFunctionAdd.apply(100, 50));\n  System.out.println(biFunctionMinus.apply(100, 50));\n  System.out.println(biFunctionMultiple.apply(100, 50));\n}\n```\n\n[실행 결과]\n\n```html\nbiFunctionAdd Result: 150\nbiFunctionMinus Result: 50\nbiFunctionMultiple Result: 5000\n```\n\n## 🔹 Arrays.asList\n\n일반 배열을 ArrayList로 변환한다.\n\n`List\u003cString\u003e list = Arrays.asList(arr);`\n\nArrays.asList()는 Arrays의 private 정적 클래스인 ArrayList를 리턴한다. **java.util.ArrayList 클래스와는 다른 클래스**이다.\n\njava.util.Arrays.ArrayList 클래스는 set(), get(), contains() 메서드를 가지고 있지만 **원소를 추가하는 메서드는 가지고 있지 않기 때문에 사이즈를 바꿀 수 없다.**\n\n```java\npackage Test;\nimport java.util.List;\nimport java.util.ArrayList;\nimport java.util.Arrays;\n \n \npublic class TestArrayAsList {\n   public static void main(String[] args) {\n      String[] strs = {\"alpha\", \"beta\", \"charlie\"};\n      System.out.println(Arrays.toString(strs));   // [alpha, beta, charlie]\n \n      List\u003cString\u003e lst = Arrays.asList(strs); // new ArrayList\u003cString\u003e(); 대신에 사용\n      System.out.println(lst);  // [alpha, beta, charlie]\n \n      lst.add(\"ttt\");     // error : asList()로 List를 생성하면 원소를 새롭게 추가할 수 없음\n \n      // Changes in array or list write thru\n      strs[0] += \"88\";\n      lst.set(2, lst.get(2) + \"99\"); // 2번째 인덱스 원소에 charlie99 넣음\n      System.out.println(Arrays.toString(strs)); // [alpha88, beta, charlie99]\n      System.out.println(lst);  // [alpha88, beta, charlie99]\n \n      // Initialize a list using an array\n      List\u003cInteger\u003e lstInt = Arrays.asList(22, 44, 11, 33);\n      System.out.println(lstInt);  // [22, 44, 11, 33]\n   }\n}\n```\n\n---\n\n위에 소스를 보면 lst에 담겨있는 두 번째 인덱스에 데이터를 수정했는데 **원본 배열의 데이터까지 변경이 됐다.**\n\nList는 내부 구조가 배열로 만들어져 있다. 따라서 asList()를 사용해서 반환되는 List도 배열을 갖게 된다. 이때, asList()를 사용해서 List 객체를 만들 때 새로운 배열 객체를 만드는 것이 아니라, **원본 배열의 주소값**을 가져오게 된다.\n\n따라서 asList()를 사용해서 내용을 수정하면 원본 배열도 함께 바뀌게 되고, 원본 배열을 수정하면 그 배열로 만들어뒀던 asList()를 이용한 List 내용도 바뀌게 된다.\n\n이러한 이유 때문에 Arrays.asList()로 만든 List에 새로운 원소를 추가하거나 삭제 할 수 없다.따라서 Arrays.asList()는 배열의 내용을 수정하려고 할 때 List로 바꿔서 편리하게 사용하기 위함.\n\n→\n\n1. **일관성과 통일성 유지**: **`List`** 인터페이스는 배열과 유사한 작업을 수행할 수 있는 메서드를 제공한다. 예를 들어 **`get`**, **`set`**, **`size`** 등의 메서드를 사용하여 배열과 `List`를 일관되게 다룰 수 있습니다. 이로써 코드의 일관성을 유지하고 유지보수를 단순화할 수 있습니다.\n2. **컬렉션 라이브러리 활용**: **`List`** 인터페이스를 구현한 컬렉션 클래스(예: **`ArrayList`**, **`LinkedList`**)는 배열보다 풍부한 기능을 제공.\n3. **호환성**: 기존의 배열 기반 코드를 컬렉션 기반 코드로 쉽게 이전할 수 있도록 한다. 이렇게 하면 코드의 유지보수가 더 쉬워지고 새로운 기능을 추가하기도 용이해진다.\n\n---\n\n만약, **진짜 ArrayList를 받기 위해서는 다음과 같이 변환하면 된다.**\n\nArrayList 생성자는 **java.util.Arrays.ArrayList의 상위(super) 클래스인 Collection Type도 받아들일 수 있다.**\n\n`List\u003cString\u003e lst = new ArrayList\u003cString\u003e(Arrays.asList(strs));`\n\n```java\npackage Test;\nimport java.util.List;\nimport java.util.ArrayList;\nimport java.util.Arrays;\n \n \npublic class TestArrayAsList {\n   public static void main(String[] args) {\n      String[] strs = {\"alpha\", \"beta\", \"charlie\"};\n      System.out.println(Arrays.toString(strs));   // [alpha, beta, charlie]\n \n      List\u003cString\u003e lst = new ArrayList\u003cString\u003e(Arrays.asList(strs)); \n      System.out.println(lst);  // [alpha, beta, charlie]\n \n      lst.add(\"ttt\");     // 이제는 에러가 나지 않고 데이터를 추가 시킬 수 있다.\n \n      // Changes in array or list write thru\n      strs[0] += \"88\";\n      lst.set(2, lst.get(2) + \"99\"); // 2번째 인덱스 원소에 charlie99 넣음\n      System.out.println(Arrays.toString(strs)); // [alpha88, beta, charlie]\n      System.out.println(lst);  // [alpha, beta, charlie99, ttt]\n \n      // Initialize a list using an array\n      List\u003cInteger\u003e lstInt = Arrays.asList(22, 44, 11, 33);\n      System.out.println(lstInt);  // [22, 44, 11, 33]\n   }\n}\n```\n\n이제는 원본 배열과 lst 객체에 담겨있는 배열 데이터는 별개의 주소값이라고 보면 된다.\n\n## 🚧 주의할 점\n\n아래와 같은 상황에 처음의 stack.pop()에 firstNumber를 붙여주게 된다면 \"-\" 의 상황에서 잘못된 값이 나오게 된다. \n이유는 pop() 메서드의 경우 마지막 값이 먼저 나오게 되는데 이렇게 된다면 뒤에서 앞의 값을 \"-\"하는 상황이 나오기 때문에 주의해야 한다.\n\n```java\n@Override\npublic long calculate(List\u003cString\u003e postfix) {\n\n        Stack\u003cLong\u003e stack = new Stack\u003c\u003e();\n        for (String token : postfix) {\n            if (token.matches(pattern)) {\n                stack.push(Long.parseLong(token));\n            } else {\n                long secondNumber = stack.pop(); // pop의 경우 나중에 들어온 것 부터 나오기 때문에 second가 먼저 나온다.\n                long firstNumber = stack.pop();\n                Operator operator = Operator.getOperator(token);\n\n                stack.push(operator.calculate(firstNumber, secondNumber));\n            }\n        }\n\n        return stack.pop();\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoxknow%2Fdev_practice","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnoxknow%2Fdev_practice","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoxknow%2Fdev_practice/lists"}