diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..c0e01feb8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,53 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!src/main/** +!src/test/** + +### macOS ### +.DS_Store + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ +# Ignore Gradle build output directory +build + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Avoid ignore Gradle wrappper properties +!gradle-wrapper.properties + +# Cache of project +.gradletasknamecache + +### Gradle Patch ### +# Java heap dump +*.hprof \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..2d6c6dc89 --- /dev/null +++ b/build.gradle @@ -0,0 +1,20 @@ +plugins { + id 'java' +} + +group = 'com.programmers.calculator' +version = '1.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation platform('org.junit:junit-bom:5.9.1') + testImplementation 'org.junit.jupiter:junit-jupiter' + testImplementation 'org.assertj:assertj-core:3.22.0' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..9835fef44 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,97 @@ +## ๐Ÿ“Œ ๊ณผ์ œ ์„ค๋ช… + +- [x] ์ฝ˜์†”๋กœ ๊ตฌํ˜„ +- [x] OOP ๊ณ„์‚ฐ๊ธฐ ๊ตฌํ˜„ + - [x] ๋”ํ•˜๊ธฐ + - [x] ๋นผ๊ธฐ + - [x] ๊ณฑํ•˜๊ธฐ + - [x] ๋‚˜๋ˆ„๊ธฐ + - [x] ์šฐ์„ ์ˆœ์œ„ (์‚ฌ์น™์—ฐ์‚ฐ) +- [x] ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๊ตฌํ˜„ +- [x] ๊ณ„์‚ฐ ์ด๋ ฅ์„ ๋งต์œผ๋กœ ๋ฐ์ดํ„ฐ ์ €์žฅ ๊ธฐ๋Šฅ + - ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋™์ž‘ํ•˜๋Š” ๋™์•ˆ DB ์™ธ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ• ๊ณ ๋ฏผ +- [x] ์ •๊ทœ์‹ ์‚ฌ์šฉ + +
+ +## ๐Ÿ‘ฉโ€๐Ÿ’ป ์š”๊ตฌ ์‚ฌํ•ญ๊ณผ ๊ตฌํ˜„ ๋‚ด์šฉ + +1. ์ž…๋ ฅ๊ณผ ์ถœ๋ ฅ์ด ํ˜„์žฌ๋Š” ์ฝ˜์†”์ด์ง€๋งŒ, ๊ฐ๊ธฐ ๋‹ค๋ฅธ ํ˜•ํƒœ๋กœ ๋ฐ”๋€” ์ˆ˜ ์žˆ์–ด์„œ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์ถ”์ƒํ™”ํ•˜๊ณ  ์ด๋ฅผ ๊ตฌํ˜„ํ•œ InputConsole๊ณผ OutputConsole์ด ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค. +2. Calculator ํด๋ž˜์Šค์—์„œ ์ž…๋ ฅ์„ ๋ฐ›๊ฒŒ ํ–ˆ๋‹ค๊ฐ€ ๊ณ„์‚ฐ์ด๋ผ๋Š” ์ฑ…์ž„์—์„œ ๋ฉ€์–ด์ง€๋Š” ๊ฒƒ ๊ฐ™์•„์„œ Controller๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์—ฐ๊ฒฐ์‹œ์ผฐ์Šต๋‹ˆ๋‹ค. +3. Enum ํด๋ž˜์Šค๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์„ ํƒ ๊ฐ€๋Šฅํ•œ ๋ฉ”๋‰ด๋ฅผ ์ •์˜ํ•˜์˜€๊ณ  ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ์ง„ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค. +4. Calculator ํด๋ž˜์Šค์—์„œ๋Š” ๊ณ„์‚ฐ์ด๋ผ๋Š” ๋™์ž‘๋งŒ ์ˆ˜ํ–‰ํ•˜๊ณ  Expression์ด๋ผ๋Š” ํ‘œํ˜„์‹ ํด๋ž˜์Šค์—์„œ ํ‘œํ˜„์‹๊ณผ ๊ด€๋ จ๋œ ๋ชจ๋“  ์ฑ…์ž„์„ ๊ฐ–์Šต๋‹ˆ๋‹ค. + - ์—ฐ์‚ฐ์ž์— ๋Œ€ํ•œ ์ฑ…์ž„์€ Enum ํด๋ž˜์Šค๋ฅผ ํ™œ์šฉํ•œ Operator์—์„œ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +5. ์ €์žฅ์†Œ๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ, HistoryRepository๋กœ ์ถ”์ƒํ™”ํ•˜์˜€๊ณ  ์ด๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ถ”๊ฐ€๋กœ, CalculationHistory ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•˜์—ฌ ์ €์žฅ์‹œํ‚ค๋„๋ก ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. +6. ๊ธฐ์กด์— ์ •๊ทœ์‹์„ ๋‹ค๋ฃจ๋˜ ๊ณณ์€ Expression ์ด์—ˆ์œผ๋‚˜, ์ˆ˜์‹ ๊ณ„์‚ฐ๋งˆ๋‹ค Pattern์„ ์ƒ์„ฑํ•˜๋Š” ๋น„์šฉ์ด ํฌ๋‹ค๊ณ  ์ƒ๊ฐํ•˜์—ฌ, ๋”ฐ๋กœ util๋กœ ๋บ์Šต๋‹ˆ๋‹ค. + - Pattern ์ƒ์„ฑ ๋น„์šฉ์— ๊ด€ํ•œ ๊ทผ๊ฑฐ๋Š”, ํด๋ž˜์Šค๋ฅผ ์‚ดํŽด๋ณด๋ฉด `new Pattern()` ํ•˜๋Š” ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค. + +
+ +## ๐Ÿ˜ญ 1์ฐจ ์ž๊ธฐ์ฃผ๋„์  ๋ฆฌ๋ทฐ ํ”ผ๋“œ๋ฐฑ + +**์˜์ˆ˜๋‹˜ ํ”ผ๋“œ๋ฐฑ** +1. ๊ฐ’์˜ ๊ฒ€์ฆ์—๋Š” ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. (`Expression` ํด๋ž˜์Šค์˜ `isNumber()` ๋ฉ”์„œ๋“œ) +2. `Calculator` ํด๋ž˜์Šค์—์„œ ํ•„๋“œ๋กœ `expression`์„ ๊ฐ€์ง€๋Š” ๊ฒƒ์— ์˜๋ฌธ์ด ๋“ญ๋‹ˆ๋‹ค. +3. `Expression` ํด๋ž˜์Šค๊ฐ€ ๋„ˆ๋ฌด ๋น„๋Œ€ํ•˜๊ณ  ํŠนํžˆ `converToPostfix()` ๋ฉ”์„œ๋“œ๋ฅผ ์ตœ๋Œ€ํ•œ ์ชผ๊ฐœ๋ด…์‹œ๋‹ค. +4. `if-else` ๋ณด๋‹จ `switch`๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ฆฌํŒฉํ† ๋ง ํ•ด๋ด…์‹œ๋‹ค. +5. ์ตœ๋Œ€ํ•œ 3 depth๋ฅผ ๋„˜์ง€ ์•Š๊ฒŒ ๋ฉ”์„œ๋“œ๋ฅผ ๋ฆฌํŒฉํ† ๋ง ํ•ด๋ด…์‹œ๋‹ค. +6. ์—ฌ๋Ÿฌ ๊ฐ์ฒด๊ฐ€ ์ฐธ์กฐํ•˜๋Š” ํ˜•ํƒœ๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด, static ์‚ฌ์šฉ์— ๊ด€ํ•˜์—ฌ ๊ณ ๋ฏผํ•ด๋ด…์‹œ๋‹ค. + - OutOfMemory์— ๊ด€ํ•ด์„œ๋„ ๊ณ ๋ คํ•ด๋ด…์‹œ๋‹ค. + - ๊ฐ€๋Šฅํ•œ static์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์ฝ”๋“œ๋ฅผ ์งœ๋ด…์‹œ๋‹ค. +7. Regex๋Š” ์ƒ์ˆ˜๋ฅผ ๋ชจ์•„๋†“๊ธฐ ์œ„ํ•œ ์žฅ์†Œ๊ฐ™์•„๋ณด์ด๋Š”๋ฐ, ์–ด๋–ค ๊ณ ๋ฏผ์„ ํ•ด๋ด์•ผํ• ๊นŒ์š”? ์ƒ๊ฐํ•ด๋ด…์‹œ๋‹ค. + +
+ +**ํ‘๊ตฌ๋‹˜ ํ”ผ๋“œ๋ฐฑ** +1. ํ˜„์žฌ ๋ชจ๋‘ ๊ตฌํ˜„์ธ ๊ฒƒ ๊ฐ™๊ณ , ํ™•์žฅ์— ๋„ˆ๋ฌด ๋‹ซํ˜€ ์žˆ์Šต๋‹ˆ๋‹ค. +2. ๊ถ๊ธˆํ•œ ๊ฒƒ์„ ์„ค๋ช…ํ•˜๊ณ  ์‹ถ์€ ์˜๋„๋Š” ์•Œ๊ฒ ์œผ๋‚˜, ์ฝ”๋“œ๋ฆฌ๋ทฐ ์š”์ฒญํ•˜๋Š” ์ž…์žฅ์—์„œ ์ฝ”๋“œ๋ฅผ ์ฃผ์„์ฒ˜๋ฆฌํ•œ ๊ฒƒ์€ ๋ฐ˜๋“œ์‹œ ์—†์• ๋ด…์‹œ๋‹ค. +3. ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜์ง€ ๋ชปํ•˜๊ฒŒ ํ•˜๊ณ  ์‹ถ์„ ๋•Œ, ๋ฐฉ์–ด์ ์œผ๋กœ ์ฝ”๋“œ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์„ ์ƒ๊ฐํ•ฉ์‹œ๋‹ค. +4. 1๊ฐœ์˜ ์ฝ˜์†”๋กœ ์ž…์ถœ๋ ฅ ํ•˜๋Š” ์ƒํ™ฉ์ธ๋ฐ, `InputConsole`๊ณผ `OutputConsole`์ด ๋งˆ์น˜ 2๊ฐœ์˜ ์ฝ˜์†”์ฒ˜๋Ÿผ ๋ณด์ž…๋‹ˆ๋‹ค. +5. ๊ฐ์ฒด์ง€ํ–ฅ์  ์„ค๊ณ„๊ฐ€ ์–ด๋ ต๋‹ค๋ฉด, ์ €๋งŒ์˜ ๋ฐฉ์‹์ธ๋ฐ, logic โ†’ behavior โ†’ data ๋ฅผ ์ƒ๊ฐํ•ด๋ด…์‹œ๋‹ค. + - logic : data๋ฅผ ์ด์šฉํ•œ behavior์˜ ์ „์ฒด์ ์ธ ํ๋ฆ„ + - behavior : data๋ฅผ ์ด์šฉํ•˜๋Š” ์–ด๋– ํ•œ ํ–‰์œ„ + - data : ๋ง ๊ทธ๋Œ€๋กœ ๋ฐ์ดํ„ฐ, ๊ฐ์ฒด + - logic์€ behavior์—, behavior์€ data์— ์˜์กด(์‚ฌ์šฉ) + - ๊ฐ๊ฐ interaction(์ƒํ˜ธ์ž‘์šฉ)์— ๊ด€ํ•ด์„œ๋„ ์ƒ๊ฐํ•ด๋ณผ ๊ฒƒ +6. ๋ฌด์กฐ๊ฑด ๋ฉ”์„œ๋“œ๋กœ ํ‘œํ˜„ํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ๊ฐ์ฒด๊ฐ„ ์ƒํ˜ธ์ž‘์šฉ์ด ๋ถ€์กฑํ•œ ์ง€ ์ƒ๊ฐํ•ด๋ด…์‹œ๋‹ค. +7. ๋ฌด์กฐ๊ฑด ์ ์ธ set ์ง€์–‘์€ ํ•˜์ง€๋ง๊ณ , ์–ด๋–ค ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š”์ง€, ์–ด๋–ค ์ด์œ  ๋•Œ๋ฌธ์ธ์ง€ ์•Œ๊ณ  set์„ ์ง€์–‘ํ•ด๋ด…์‹œ๋‹ค. ๋„ˆ๋ฌด ๋ฌด์„œ์›Œํ•˜์ง€ ๋งˆ์„ธ์š”. + +
+ +## ๐Ÿ˜ค ํ”ผ๋“œ๋ฐฑ ๊ธฐ๋ฐ˜ ๋ฆฌํŒฉํ† ๋ง + +- [x] Console ๋‹จ์ผํ™” ๋ฐ ๊ตฌํ˜„์ฒด๊ฐ€ ์•„๋‹Œ ์ถ”์ƒ์ฒด์— ์˜์กดํ•˜๋„๋ก +- [x] Controller ํด๋ž˜์Šค ๋ฆฌํŒฉํ† ๋ง + - [x] logic โ†’ behavior โ†’ data ๊ณ ๋ คํ–ˆ๋Š”๊ฐ€? + - [x] ๊ฐ์ฒด๊ฐ„ ์ƒํ˜ธ์ž‘์šฉ์„ ๊ณ ๋ คํ–ˆ๋Š”๊ฐ€? +- [x] Expression ํด๋ž˜์Šค ๋ฆฌํŒฉํ† ๋ง + - [x] 3 depth๋ฅผ ๋„˜์ง€ ์•Š๋Š”๊ฐ€? + - [x] ์ถ”์ƒํ™”๋ฅผ ๊ณ ๋ คํ•˜์˜€๋Š”๊ฐ€? +- [x] Regex์— ๋ฐฉ์–ด ์ฝ”๋”ฉ์„ ์ ์šฉํ–ˆ๋Š”๊ฐ€? + + +## โœ… PR ํฌ์ธํŠธ & ๊ถ๊ธˆํ•œ ์  + +**PR ํฌ์ธํŠธ** + +- Expression ํด๋ž˜์Šค์˜ ๊ธฐ๋Šฅ์ด ๋น„๋Œ€ํ•œ ๊ฒƒ ๊ฐ™์•„์„œ ๋‚˜๋ˆด์Šต๋‹ˆ๋‹ค. + - Converter, Evaluator ์ถ”์ƒ์ฒด๋ฅผ ๋งŒ๋“ค๊ณ  ๊ทธ๋ฅผ ๊ตฌํ˜„ํ•œ ๊ตฌํ˜„์ฒด๋ฅผ ํ™œ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. + - Calculator ์ถ”์ƒ์ฒด๋ฅผ ๊ตฌํ˜„ํ•œ ๊ตฌํ˜„์ฒด์—์„œ ์กฐ๋ฆฝํ•˜์—ฌ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. + - Parser๋„ ์ถ”๊ฐ€ํ–ˆ์—ˆ๋Š”๋ฐ, ๋งก๋Š” ์ฑ…์ž„์ด Regex์—์„œ ์—ญํ• ๊ณผ ๋น„์Šทํ•˜์—ฌ ์—†์• ๊ณ  ์ด๊ด€ํ–ˆ์Šต๋‹ˆ๋‹ค. +- ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๋Š” ์ˆ˜์‹์„ ๊ฐ์‹ธ๊ธฐ ์œ„ํ•˜์—ฌ CalculationResult์™€ Expression vo๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. +- Regex์˜ ์—ญํ• ์ด ์ƒ์ˆ˜๋งŒ ์ €์žฅํ•˜๋Š” ๊ฒƒ์ด๋ผ์„œ util๊ณผ ๋งž์ง€ ์•Š์•„์„œ enum์œผ๋กœ ๋”ฐ๋กœ ๋บ์Šต๋‹ˆ๋‹ค. + +
+ +**๊ถ๊ธˆํ•œ ์ ** + +1. logic behavior data๋ฅผ ์ตœ๋Œ€ํ•œ ๊ณ ๋ คํ•ด๋ดค๋Š”๋ฐ, ์‚ฌ์‹ค ์•„์ง๋„ ๊ฐ์ด ์ž˜ ์žกํžˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ตœ๋Œ€ํ•œ ์ถ”์ƒํ™”ํ•ด๋ณด๋ ค๊ณ  ํ–ˆ๋Š”๋ฐ, ์—ญํ• ์ด ๋„ˆ๋ฌด ์ž‘๋‚˜? ๊ณผํ•œ ์ถ”์ƒํ™”์ธ๊ฐ€? ๋ผ๋Š” ๊ณ ๋ฏผ์ด ๊ณ„์† ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค. +2. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ๋ฏธํกํ•˜์—ฌ, ์˜์ˆ˜๋‹˜ ๊ฐ•์˜๋ฅผ ๋“ฃ๊ณ  ๊ฐ์ด ์กฐ๊ธˆ ์žกํ˜€์„œ ์ž‘์„ฑํ•ด๋ดค๋Š”๋ฐ, ๋ฐฉ์‹์ด ๋งž๋Š”์ง€ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค. +3. ํ™์„ญ๋‹˜ ํ”ผ๋“œ๋ฐฑ์„ ๋ดค์„ ๋•Œ, ๊ณตํ•™์šฉ ๊ณ„์‚ฐ๊ธฐ์™€ ์‚ฌ์น™์—ฐ์‚ฐ๋งŒ ๊ฐ€๋Šฅํ•œ ๊ณ„์‚ฐ๊ธฐ๋ฅผ ๊ณ ๋ คํ•ด๋ณด๋ ค ํ–ˆ์Šต๋‹ˆ๋‹ค. + - ํ˜„์žฌ, Operator enum์— ์‚ฌ์น™์—ฐ์‚ฐ์ด ๋ชจ๋‘ ๋ชฐ์•„๋‘๊ณ  enum์˜ values()๋ฅผ ๋ฐ˜๋ณต๋Œ๋ฆฌ๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•ด๋†”์„œ enum์— ์ •์˜๋˜์–ด ์žˆ๋‹ค๋ฉด ๋ฐ˜ํ™˜ํ•ด์ฃผ๊ณ  ์—†์œผ๋ฉด ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋Š” ์ƒํƒœ์ž…๋‹ˆ๋‹ค. + - enum์— ๊ธฐ๋Šฅ๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๊ณตํ•™์šฉ ๊ณ„์‚ฐ๊ธฐ๊ฐ€ ์ถ”๊ฐ€๋˜๋Š”๊ฑด ๋ฌธ์ œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. + - enum์— ๋‹ค๋ฅธ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์‚ฌ์น™์—ฐ์‚ฐ๋งŒ ๊ฐ€๋Šฅํ•œ ๊ณ„์‚ฐ๊ธฐ๊ฐ€ ๋‹ค๋ฅธ ๊ธฐ๋Šฅ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํ™ฉ์ด ๋˜์–ด๋ฒ„๋ฆฝ๋‹ˆ๋‹ค. + - ์ด์— ๋Œ€ํ•˜์—ฌ ์ƒ๊ฐํ•œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. + - ๊ณตํ•™์šฉ ๊ณ„์‚ฐ๊ธฐ enum์„ ํ•˜๋‚˜ ๋” ๋งŒ๋“ค ๋•Œ, ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ด์šฉํ•ด ํ™•์žฅ ๊ฐ€๋Šฅํ•œ Enum Type์ด ๋˜๋„๋ก ๋ฆฌํŒฉํ† ๋ง ํ•˜๋Š” ๊ฒƒ ์ž…๋‹ˆ๋‹ค. [์•„์ดํ…œ38 - ์ธํ„ฐํŽ˜์ด์Šค ํ™œ์šฉ Enum Type ํ™•์žฅ](https://medium.com/lucky-sonnie/item-38-%ED%99%95%EC%9E%A5%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94-enum-type%EC%9D%B4-%ED%95%84%EC%9A%94%ED%95%98%EB%A9%B4-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC-9c9f78334a5e)๋ฅผ ์ฐธ๊ณ ํ•˜์˜€์Šต๋‹ˆ๋‹ค. + - enum์„ ์—†์• ๊ณ  FourArithmeticStrategy ๋ผ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฐ๊ฐ ์‚ฌ์น™์—ฐ์‚ฐ์ด ์žˆ๊ณ , ๊ณตํ•™์šฉ ์ „๋žต ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด์„œ ๊ฑฐ๊ธฐ์— ์‹ฌํ™”์ ์ธ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๊ณ , ๊ธฐ๋ณธ ๊ณ„์‚ฐ๊ธฐ์—๋Š” ์‚ฌ์น™์—ฐ์‚ฐ ์ „๋žต๋งŒ ์ ์šฉ, ๊ณตํ•™์šฉ ๊ณ„์‚ฐ๊ธฐ์—๋Š” ์‚ฌ์น™์—ฐ์‚ฐ์ „๋žต + ์‹ฌํ™”์ „๋žต ์ ์šฉ ์ด๋ ‡๊ฒŒ ๊ฐ€๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. + - ์–ด๋– ํ•œ ๋ฐฉ๋ฒ•์ด ๋” ๊ดœ์ฐฎ์„์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค. \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..249e5832f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..2d4d62871 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Jun 08 01:00:05 KST 2023 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..1b6c78733 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright ยฉ 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions ยซ$varยป, ยซ${var}ยป, ยซ${var:-default}ยป, ยซ${var+SET}ยป, +# ยซ${var#prefix}ยป, ยซ${var%suffix}ยป, and ยซ$( cmd )ยป; +# * compound commands having a testable exit status, especially ยซcaseยป; +# * various built-in commands including ยซcommandยป, ยซsetยป, and ยซulimitยป. +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..107acd32c --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..bcf30bf6c --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'java-calculator' + diff --git a/src/main/java/com/programmers/Application.java b/src/main/java/com/programmers/Application.java new file mode 100644 index 000000000..ef743155c --- /dev/null +++ b/src/main/java/com/programmers/Application.java @@ -0,0 +1,39 @@ +package com.programmers; + +import com.programmers.calculator.controller.CalculatorController; +import com.programmers.calculator.domain.component.Evaluator; +import com.programmers.calculator.domain.component.NotationConverter; +import com.programmers.calculator.domain.component.PostfixConverter; +import com.programmers.calculator.domain.component.PostfixEvaluator; +import com.programmers.calculator.domain.core.Calculator; +import com.programmers.calculator.domain.core.FourArithmeticCalculator; +import com.programmers.calculator.repository.HistoryMapRepository; +import com.programmers.calculator.repository.HistoryRepository; +import com.programmers.calculator.view.Console; +import com.programmers.calculator.view.Input; +import com.programmers.calculator.view.InputReader; +import com.programmers.calculator.view.Output; +import com.programmers.calculator.view.OutputWriter; + +public class Application { + public static void main(String[] args) { + + // view + Input input = new InputReader(); + Output output = new OutputWriter(); + Console console = new Console(input, output); + + // domain + NotationConverter converter = new PostfixConverter(); + Evaluator evaluator = new PostfixEvaluator(); + Calculator calculator = new FourArithmeticCalculator(converter, evaluator); + + // repository + HistoryRepository repository = new HistoryMapRepository(); + + // controller + CalculatorController calculatorController = new CalculatorController(console, calculator, repository); + calculatorController.run(); + + } +} diff --git a/src/main/java/com/programmers/calculator/constant/OptionType.java b/src/main/java/com/programmers/calculator/constant/OptionType.java new file mode 100644 index 000000000..99fe7dbc7 --- /dev/null +++ b/src/main/java/com/programmers/calculator/constant/OptionType.java @@ -0,0 +1,30 @@ +package com.programmers.calculator.constant; + +import java.util.Arrays; + +public enum OptionType { + EXIT("0"), + HISTORY("1"), + CALCULATION("2"); + + private static final OptionType[] OPTION_TYPES = OptionType.values(); + + private final String inputOption; + + OptionType(String inputOption) { + this.inputOption = inputOption; + } + + public String getInputOption() { + return inputOption; + } + + public static OptionType of(String inputOption) { + return Arrays.stream(OPTION_TYPES) + .filter(optionType -> optionType.inputOption.equals(inputOption)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("์œ ํšจํ•˜์ง€ ์•Š์€ ๋ฉ”๋‰ด์ž…๋‹ˆ๋‹ค.")); + } + +} + diff --git a/src/main/java/com/programmers/calculator/constant/RegexEnum.java b/src/main/java/com/programmers/calculator/constant/RegexEnum.java new file mode 100644 index 000000000..6d1dd913e --- /dev/null +++ b/src/main/java/com/programmers/calculator/constant/RegexEnum.java @@ -0,0 +1,46 @@ +package com.programmers.calculator.constant; + +import com.programmers.calculator.domain.vo.Expression; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public enum RegexEnum { + + FOUR_ARITHMETIC("\\d+|[-+*/]"), + NUMERIC("\\d+"); + + private final String regex; + private final Pattern pattern; + + RegexEnum(String regex) { + this.regex = regex; + this.pattern = Pattern.compile(regex); + } + + public String getRegex() { + return regex; + } + + public Pattern getPattern() { + return pattern; + } + + public static boolean isNumeric(String token) { + return token.matches(RegexEnum.NUMERIC.getRegex()); + } + + public static List parseToTokens(Expression expression) { + List tokens = new ArrayList<>(); + Matcher matcher = RegexEnum.FOUR_ARITHMETIC.getPattern().matcher(expression.toString()); + + while (matcher.find()) { + tokens.add(matcher.group()); + } + + return tokens; + } +} + diff --git a/src/main/java/com/programmers/calculator/controller/CalculatorController.java b/src/main/java/com/programmers/calculator/controller/CalculatorController.java new file mode 100644 index 000000000..ac3fbb292 --- /dev/null +++ b/src/main/java/com/programmers/calculator/controller/CalculatorController.java @@ -0,0 +1,84 @@ +package com.programmers.calculator.controller; + +import com.programmers.calculator.constant.OptionType; +import com.programmers.calculator.domain.core.Calculator; +import com.programmers.calculator.domain.vo.CalculationResult; +import com.programmers.calculator.domain.vo.Expression; +import com.programmers.calculator.repository.CalculationHistory; +import com.programmers.calculator.repository.HistoryRepository; +import com.programmers.calculator.view.Console; + +import java.util.List; + +public class CalculatorController { + + private final Console console; + private final Calculator calculator; + private final HistoryRepository repository; + + public CalculatorController(Console console, Calculator calculator, HistoryRepository repository) { + this.console = console; + this.calculator = calculator; + this.repository = repository; + } + + public void run() { + + while (true) { + + try { + console.outputOption(); + OptionType inputValue = OptionType.of(console.inputOption()); + + switch (inputValue) { + case EXIT: + exitCalculator(); + break; + case HISTORY: + loadCalculationHistory(); + break; + case CALCULATION: + processCalculation(); + break; + default: + errorDetection(); + } + } catch (IllegalArgumentException | ArithmeticException e) { + e.printStackTrace(); + } + + } + } + + private void exitCalculator() { + console.outputExit(); + System.exit(0); + } + + private void loadCalculationHistory() { + List calculationHistoryList = repository.findAll(); + console.outputHistory(calculationHistoryList); + } + + private void processCalculation() { + Expression expression = new Expression(console.inputExpression()); + CalculationResult calculationResult = calculator.calculate(expression); + + printCalculationResult(calculationResult); + saveCalculationResult(expression, calculationResult); + } + + private void printCalculationResult(CalculationResult calculationResult) { + console.outputCalculation(calculationResult); + } + + private void saveCalculationResult(Expression expression, CalculationResult calculationResult) { + CalculationHistory calculationHistory = new CalculationHistory(expression, calculationResult); + repository.save(calculationHistory); + } + + private void errorDetection() { + System.exit(0); + } + +} diff --git a/src/main/java/com/programmers/calculator/domain/component/Evaluator.java b/src/main/java/com/programmers/calculator/domain/component/Evaluator.java new file mode 100644 index 000000000..b20e5eb7e --- /dev/null +++ b/src/main/java/com/programmers/calculator/domain/component/Evaluator.java @@ -0,0 +1,9 @@ +package com.programmers.calculator.domain.component; + +import com.programmers.calculator.domain.vo.CalculationResult; + +import java.util.List; + +public interface Evaluator { + CalculationResult evaluate(List convertedExpression); +} diff --git a/src/main/java/com/programmers/calculator/domain/component/NotationConverter.java b/src/main/java/com/programmers/calculator/domain/component/NotationConverter.java new file mode 100644 index 000000000..bd7389011 --- /dev/null +++ b/src/main/java/com/programmers/calculator/domain/component/NotationConverter.java @@ -0,0 +1,7 @@ +package com.programmers.calculator.domain.component; + +import java.util.List; + +public interface NotationConverter { + List convert(List tokens); +} diff --git a/src/main/java/com/programmers/calculator/domain/component/PostfixConverter.java b/src/main/java/com/programmers/calculator/domain/component/PostfixConverter.java new file mode 100644 index 000000000..82c24a8fa --- /dev/null +++ b/src/main/java/com/programmers/calculator/domain/component/PostfixConverter.java @@ -0,0 +1,45 @@ +package com.programmers.calculator.domain.component; + +import com.programmers.calculator.constant.RegexEnum; +import com.programmers.calculator.domain.core.Operator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +public class PostfixConverter implements NotationConverter { + + @Override + public List convert(List tokens) { + List postfix = new ArrayList<>(); + Stack operatorStack = new Stack<>(); + + for (String token : tokens) { + if (RegexEnum.isNumeric(token)) { + postfix.add(token); + continue; + } + + compareOperatorPriority(postfix, operatorStack, token.charAt(0)); + operatorStack.push(token.charAt(0)); + } + + pushUntilEmptyStack(postfix, operatorStack); + + return postfix; + } + + private void compareOperatorPriority(List postfix, Stack operatorStack, char tokenChar) { + while (!operatorStack.isEmpty() && (Operator.of(tokenChar).getPriority() <= Operator.of(operatorStack.peek()).getPriority())) { + char popOperator = operatorStack.pop(); + postfix.add(String.valueOf(popOperator)); + } + } + + private void pushUntilEmptyStack(List postfix, Stack operatorStack) { + while (!operatorStack.isEmpty()) { + postfix.add(operatorStack.pop().toString()); + } + } + +} diff --git a/src/main/java/com/programmers/calculator/domain/component/PostfixEvaluator.java b/src/main/java/com/programmers/calculator/domain/component/PostfixEvaluator.java new file mode 100644 index 000000000..b491e11bb --- /dev/null +++ b/src/main/java/com/programmers/calculator/domain/component/PostfixEvaluator.java @@ -0,0 +1,39 @@ +package com.programmers.calculator.domain.component; + +import com.programmers.calculator.constant.RegexEnum; +import com.programmers.calculator.domain.core.Operator; +import com.programmers.calculator.domain.vo.CalculationResult; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Stack; + +public class PostfixEvaluator implements Evaluator { + + @Override + public CalculationResult evaluate(List convertedExpression) { + Stack operandStack = new Stack<>(); + + for (String token : convertedExpression) { + if (RegexEnum.isNumeric(token)) { + operandStack.push(new CalculationResult(new BigDecimal(token))); + continue; + } + + if (operandStack.size() < 2) { + throw new IllegalArgumentException("์ž˜๋ชป๋œ ์ˆ˜์‹์ž…๋‹ˆ๋‹ค."); + } + + CalculationResult operand2 = operandStack.pop(); + CalculationResult operand1 = operandStack.pop(); + CalculationResult result = Operator.of(token.charAt(0)).getFunction().apply(operand1, operand2); + operandStack.push(result); + } + + if (operandStack.size() != 1) { + throw new IllegalArgumentException("์ž˜๋ชป๋œ ์ˆ˜์‹์ž…๋‹ˆ๋‹ค."); + } + + return operandStack.pop(); + } +} diff --git a/src/main/java/com/programmers/calculator/domain/core/Calculator.java b/src/main/java/com/programmers/calculator/domain/core/Calculator.java new file mode 100644 index 000000000..0ce5c0daa --- /dev/null +++ b/src/main/java/com/programmers/calculator/domain/core/Calculator.java @@ -0,0 +1,8 @@ +package com.programmers.calculator.domain.core; + +import com.programmers.calculator.domain.vo.CalculationResult; +import com.programmers.calculator.domain.vo.Expression; + +public interface Calculator { + CalculationResult calculate(Expression expression); +} diff --git a/src/main/java/com/programmers/calculator/domain/core/FourArithmeticCalculator.java b/src/main/java/com/programmers/calculator/domain/core/FourArithmeticCalculator.java new file mode 100644 index 000000000..b6e6b15b5 --- /dev/null +++ b/src/main/java/com/programmers/calculator/domain/core/FourArithmeticCalculator.java @@ -0,0 +1,28 @@ +package com.programmers.calculator.domain.core; + +import com.programmers.calculator.constant.RegexEnum; +import com.programmers.calculator.domain.component.Evaluator; +import com.programmers.calculator.domain.component.NotationConverter; +import com.programmers.calculator.domain.vo.CalculationResult; +import com.programmers.calculator.domain.vo.Expression; + +import java.util.List; + +public class FourArithmeticCalculator implements Calculator { + + private final NotationConverter converter; + private final Evaluator evaluator; + + public FourArithmeticCalculator(NotationConverter converter, Evaluator evaluator) { + this.converter = converter; + this.evaluator = evaluator; + } + + @Override + public CalculationResult calculate(Expression expression) { + List tokens = RegexEnum.parseToTokens(expression); + List postfix = converter.convert(tokens); + return evaluator.evaluate(postfix); + } + +} diff --git a/src/main/java/com/programmers/calculator/domain/core/Operator.java b/src/main/java/com/programmers/calculator/domain/core/Operator.java new file mode 100644 index 000000000..764be377b --- /dev/null +++ b/src/main/java/com/programmers/calculator/domain/core/Operator.java @@ -0,0 +1,52 @@ +package com.programmers.calculator.domain.core; + +import com.programmers.calculator.domain.vo.CalculationResult; + +import java.math.BigDecimal; +import java.util.Objects; +import java.util.function.BiFunction; + +public enum Operator { + ADDITION('+', 10, CalculationResult::add), + SUBTRACTION('-', 10, CalculationResult::subtract), + MULTIPLICATION('*', 100, CalculationResult::multiply), + DIVISION('/', 100, (o1, o2) -> { + if (Objects.equals(o2.getValue(), BigDecimal.ZERO)) { + throw new ArithmeticException("0์œผ๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); + } + return o1.divide(o2); + }); + + private static final Operator[] OPERATORS = Operator.values(); + + private final char symbol; + private final int priority; + private final BiFunction function; + + Operator(char symbol, int priority, BiFunction function) { + this.symbol = symbol; + this.priority = priority; + this.function = function; + } + + public char getSymbol() { + return symbol; + } + + public int getPriority() { + return priority; + } + + public BiFunction getFunction() { + return function; + } + + public static Operator of(char inputOperator) { + for (Operator operator : OPERATORS) { + if (operator.symbol == inputOperator) { + return operator; + } + } + throw new IllegalArgumentException("์œ ํšจํ•˜์ง€ ์•Š์€ ์—ฐ์‚ฐ์ž์ž…๋‹ˆ๋‹ค."); + } +} diff --git a/src/main/java/com/programmers/calculator/domain/vo/CalculationResult.java b/src/main/java/com/programmers/calculator/domain/vo/CalculationResult.java new file mode 100644 index 000000000..3acbc3200 --- /dev/null +++ b/src/main/java/com/programmers/calculator/domain/vo/CalculationResult.java @@ -0,0 +1,37 @@ +package com.programmers.calculator.domain.vo; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +public class CalculationResult { + private final BigDecimal value; + + public CalculationResult(BigDecimal value) { + this.value = value; + } + + public BigDecimal getValue() { + return value; + } + + public CalculationResult add(CalculationResult otherValue) { + return new CalculationResult(value.add(otherValue.getValue())); + } + + public CalculationResult subtract(CalculationResult otherValue) { + return new CalculationResult(value.subtract(otherValue.getValue())); + } + + public CalculationResult multiply(CalculationResult otherValue) { + return new CalculationResult(value.multiply(otherValue.getValue())); + } + + public CalculationResult divide(CalculationResult otherValue) { + return new CalculationResult(value.divide(otherValue.getValue(), 5, RoundingMode.HALF_UP)); + } + + @Override + public String toString() { + return value.toString(); + } +} diff --git a/src/main/java/com/programmers/calculator/domain/vo/Expression.java b/src/main/java/com/programmers/calculator/domain/vo/Expression.java new file mode 100644 index 000000000..56eeea513 --- /dev/null +++ b/src/main/java/com/programmers/calculator/domain/vo/Expression.java @@ -0,0 +1,18 @@ +package com.programmers.calculator.domain.vo; + +public class Expression { + private final String expression; + + public Expression(String expression) { + this.expression = expression; + } + + public String getExpression() { + return expression; + } + + @Override + public String toString() { + return expression; + } +} diff --git a/src/main/java/com/programmers/calculator/repository/CalculationHistory.java b/src/main/java/com/programmers/calculator/repository/CalculationHistory.java new file mode 100644 index 000000000..8cba608e1 --- /dev/null +++ b/src/main/java/com/programmers/calculator/repository/CalculationHistory.java @@ -0,0 +1,28 @@ +package com.programmers.calculator.repository; + +import com.programmers.calculator.domain.vo.CalculationResult; +import com.programmers.calculator.domain.vo.Expression; + +public class CalculationHistory { + + private Long id = 0L; + private Expression expression; + private CalculationResult result; + + public CalculationHistory(Expression expression, CalculationResult result) { + this.expression = expression; + this.result = result; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public CalculationHistory saveHistory() { + return this; + } +} diff --git a/src/main/java/com/programmers/calculator/repository/HistoryMapRepository.java b/src/main/java/com/programmers/calculator/repository/HistoryMapRepository.java new file mode 100644 index 000000000..47b05e231 --- /dev/null +++ b/src/main/java/com/programmers/calculator/repository/HistoryMapRepository.java @@ -0,0 +1,23 @@ +package com.programmers.calculator.repository; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class HistoryMapRepository implements HistoryRepository { + + private final Map store = new HashMap<>(); + private long sequence = 0L; + + @Override + public void save(CalculationHistory calculationHistory) { + calculationHistory.setId(++sequence); + store.put(calculationHistory.getId(), calculationHistory.saveHistory()); + } + + @Override + public List findAll() { + return new ArrayList<>(store.values()); + } +} diff --git a/src/main/java/com/programmers/calculator/repository/HistoryRepository.java b/src/main/java/com/programmers/calculator/repository/HistoryRepository.java new file mode 100644 index 000000000..2af208ab8 --- /dev/null +++ b/src/main/java/com/programmers/calculator/repository/HistoryRepository.java @@ -0,0 +1,8 @@ +package com.programmers.calculator.repository; + +import java.util.List; + +public interface HistoryRepository { + void save(CalculationHistory calculationHistory); + List findAll(); +} diff --git a/src/main/java/com/programmers/calculator/view/Console.java b/src/main/java/com/programmers/calculator/view/Console.java new file mode 100644 index 000000000..c9a53a5e4 --- /dev/null +++ b/src/main/java/com/programmers/calculator/view/Console.java @@ -0,0 +1,45 @@ +package com.programmers.calculator.view; + +import com.programmers.calculator.domain.vo.CalculationResult; +import com.programmers.calculator.repository.CalculationHistory; + +import java.util.List; + +public class Console { + + private final Input input; + private final Output output; + private final String newLine = System.getProperty("line.separator"); + + public Console(Input input, Output output) { + this.input = input; + this.output = output; + } + + public String inputOption() { + output.write(newLine + newLine + "์„ ํƒ : "); + return input.read(); + } + + public String inputExpression() { + return input.read(); + } + + public void outputOption() { + output.write(newLine + "0. ์ข…๋ฃŒ"); + output.write(newLine + "1. ์กฐํšŒ"); + output.write(newLine + "2. ๊ณ„์‚ฐ"); + } + + public void outputExit() { + output.write("ํ”„๋กœ๊ทธ๋žจ์ด ์ข…๋ฃŒ๋ฉ๋‹ˆ๋‹ค."); + } + + public void outputHistory(List calculationHistories) { + output.write(calculationHistories); + } + + public void outputCalculation(CalculationResult result) { + output.write(result); + } +} diff --git a/src/main/java/com/programmers/calculator/view/Input.java b/src/main/java/com/programmers/calculator/view/Input.java new file mode 100644 index 000000000..03abada4e --- /dev/null +++ b/src/main/java/com/programmers/calculator/view/Input.java @@ -0,0 +1,5 @@ +package com.programmers.calculator.view; + +public interface Input { + String read(); +} diff --git a/src/main/java/com/programmers/calculator/view/InputReader.java b/src/main/java/com/programmers/calculator/view/InputReader.java new file mode 100644 index 000000000..a209d84ed --- /dev/null +++ b/src/main/java/com/programmers/calculator/view/InputReader.java @@ -0,0 +1,17 @@ +package com.programmers.calculator.view; + +import java.util.Scanner; + +public class InputReader implements Input { + + private final Scanner scanner; + + public InputReader() { + this.scanner = new Scanner(System.in); + } + + @Override + public String read() { + return scanner.nextLine(); + } +} diff --git a/src/main/java/com/programmers/calculator/view/Output.java b/src/main/java/com/programmers/calculator/view/Output.java new file mode 100644 index 000000000..73dc10759 --- /dev/null +++ b/src/main/java/com/programmers/calculator/view/Output.java @@ -0,0 +1,12 @@ +package com.programmers.calculator.view; + +import com.programmers.calculator.domain.vo.CalculationResult; +import com.programmers.calculator.repository.CalculationHistory; + +import java.util.List; + +public interface Output { + void write(String message); + void write(List calculationHistories); + void write(CalculationResult result); +} \ No newline at end of file diff --git a/src/main/java/com/programmers/calculator/view/OutputWriter.java b/src/main/java/com/programmers/calculator/view/OutputWriter.java new file mode 100644 index 000000000..8950422d3 --- /dev/null +++ b/src/main/java/com/programmers/calculator/view/OutputWriter.java @@ -0,0 +1,25 @@ +package com.programmers.calculator.view; + +import com.programmers.calculator.domain.vo.CalculationResult; +import com.programmers.calculator.repository.CalculationHistory; + +import java.util.List; + +public class OutputWriter implements Output { + + @Override + public void write(String message) { + System.out.print(message); + } + + @Override + public void write(List calculationHistories) { + calculationHistories.forEach(System.out::println); + } + + @Override + public void write(CalculationResult result) { + System.out.println(result); + } + +} diff --git a/src/test/java/com/programmers/calculator/constant/OptionTypeTest.java b/src/test/java/com/programmers/calculator/constant/OptionTypeTest.java new file mode 100644 index 000000000..1cb7f4f00 --- /dev/null +++ b/src/test/java/com/programmers/calculator/constant/OptionTypeTest.java @@ -0,0 +1,36 @@ +package com.programmers.calculator.constant; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +class OptionTypeTest { + + @DisplayName("๋ฉ”๋‰ด ๋ฒˆํ˜ธ๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊ฒจ์ฃผ๋ฉด, ์˜ฌ๋ฐ”๋ฅธ Option ๋ฐ˜ํ™˜๋œ๋‹ค.") + @ParameterizedTest + @ValueSource(strings = {"0", "1", "2"}) + void right_option_menu(String inputOption) { + + // given + OptionType optionType = OptionType.of(inputOption); + + // then + assertThat(optionType.getInputOption()).isEqualTo(inputOption); + + } + + @DisplayName("์ž˜๋ชป๋œ ๋ฉ”๋‰ด ๋ฒˆํ˜ธ๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊ฒจ์ฃผ๋ฉด, ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.") + @ParameterizedTest + @ValueSource(strings = {"3", "a", "+"}) + void wrong_option_menu(String notInputOption) { + + // then + assertThatThrownBy(() -> OptionType.of(notInputOption)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("์œ ํšจํ•˜์ง€ ์•Š์€ ๋ฉ”๋‰ด์ž…๋‹ˆ๋‹ค."); + + } +} \ No newline at end of file diff --git a/src/test/java/com/programmers/calculator/constant/RegexEnumTest.java b/src/test/java/com/programmers/calculator/constant/RegexEnumTest.java new file mode 100644 index 000000000..6209ad6c6 --- /dev/null +++ b/src/test/java/com/programmers/calculator/constant/RegexEnumTest.java @@ -0,0 +1,46 @@ +package com.programmers.calculator.constant; + +import com.programmers.calculator.domain.vo.Expression; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class RegexEnumTest { + + @DisplayName("์ •๊ทœ์‹์— ๋งž๊ฒŒ ํ† ํฐ์ด ์ž˜ ์ชผ๊ฐœ์ง€๋Š”์ง€ ํ™•์ธ") + @ParameterizedTest + @CsvSource({ + "1 + 2 * 4 / 6", + "1+2*4/6" + }) + void expression_regex_parsing (String inputExpression) { + + // given + Expression expression = new Expression(inputExpression); + + // when + List result = RegexEnum.parseToTokens(expression); + List expectedTokens = List.of("1", "+", "2", "*", "4", "/", "6"); + + // then + assertThat(result).isEqualTo(expectedTokens); + } + + @DisplayName("์ˆซ์ž๊ฐ€ ์ˆซ์ž ์ •๊ทœ์‹์— ์ฒดํฌ ๋˜๋Š”์ง€ ํ™•์ธ") + @Test + void numeric_regex() { + assertThat(RegexEnum.isNumeric("1")).isTrue(); + } + + @DisplayName("์ˆซ์ž ์•„๋‹Œ ๊ฒƒ์ด ์ˆซ์ž ์ •๊ทœ์‹์— ์ฒดํฌ๊ฐ€ ์•ˆ๋˜๋Š”์ง€ ํ™•์ธ") + @Test + void not_numeric_regex() { + assertThat(RegexEnum.isNumeric("a")).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/programmers/calculator/domain/component/PostfixConverterTest.java b/src/test/java/com/programmers/calculator/domain/component/PostfixConverterTest.java new file mode 100644 index 000000000..f11b6fc0c --- /dev/null +++ b/src/test/java/com/programmers/calculator/domain/component/PostfixConverterTest.java @@ -0,0 +1,28 @@ +package com.programmers.calculator.domain.component; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class PostfixConverterTest { + + PostfixConverter postfixConverter = new PostfixConverter(); + + @DisplayName("ํ›„์œ„ํ‘œ๊ธฐ์‹์œผ๋กœ ๋ณ€ํ™˜ ์ž˜ ๋˜๋Š”์ง€ ํ™•์ธ") + @Test + void right_convert_postfix () { + // given + List tokens = List.of("1", "+", "2", "*", "4", "/", "6"); + List expected = List.of("1", "2", "4", "*", "6", "/", "+"); + + // when + List result = postfixConverter.convert(tokens); + + // then + assertThat(result).isEqualTo(expected); + } + +} \ No newline at end of file diff --git a/src/test/java/com/programmers/calculator/domain/component/PostfixEvaluatorTest.java b/src/test/java/com/programmers/calculator/domain/component/PostfixEvaluatorTest.java new file mode 100644 index 000000000..b887f3756 --- /dev/null +++ b/src/test/java/com/programmers/calculator/domain/component/PostfixEvaluatorTest.java @@ -0,0 +1,45 @@ +package com.programmers.calculator.domain.component; + +import com.programmers.calculator.domain.vo.CalculationResult; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +class PostfixEvaluatorTest { + + PostfixEvaluator postfixEvaluator = new PostfixEvaluator(); + + @DisplayName("ํ›„์œ„ํ‘œ๊ธฐ์‹์˜ ๊ณ„์‚ฐ์ด ์ž˜ ๋˜๋Š”์ง€ ํ™•์ธ") + @Test + void right_convert_postfix () { + + // given + List postfix = List.of("1", "2", "4", "*", "6", "*", "+"); + + // when + CalculationResult result = postfixEvaluator.evaluate(postfix); + + // then + assertThat(result.getValue()).isEqualTo(new BigDecimal(49)); + } + + @DisplayName("์ž˜๋ชป๋œ ํ›„์œ„ํ‘œ๊ธฐ์‹์ด ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋Š”์ง€ ํ™•์ธ") + @Test + void throw_wrong_evaluate_postfix () { + + // given + List postfix = List.of("1", "+", "+"); + + // then + assertThatThrownBy(() -> postfixEvaluator.evaluate(postfix)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("์ž˜๋ชป๋œ ์ˆ˜์‹์ž…๋‹ˆ๋‹ค."); + + } + +} \ No newline at end of file diff --git a/src/test/java/com/programmers/calculator/domain/core/FourArithmeticCalculatorTest.java b/src/test/java/com/programmers/calculator/domain/core/FourArithmeticCalculatorTest.java new file mode 100644 index 000000000..fb4e382c5 --- /dev/null +++ b/src/test/java/com/programmers/calculator/domain/core/FourArithmeticCalculatorTest.java @@ -0,0 +1,42 @@ +package com.programmers.calculator.domain.core; + +import com.programmers.calculator.domain.component.Evaluator; +import com.programmers.calculator.domain.component.NotationConverter; +import com.programmers.calculator.domain.component.PostfixConverter; +import com.programmers.calculator.domain.component.PostfixEvaluator; +import com.programmers.calculator.domain.vo.CalculationResult; +import com.programmers.calculator.domain.vo.Expression; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import java.math.BigDecimal; + +import static org.assertj.core.api.Assertions.assertThat; + +class FourArithmeticCalculatorTest { + + NotationConverter converter = new PostfixConverter(); + Evaluator evaluator = new PostfixEvaluator(); + FourArithmeticCalculator calculator = new FourArithmeticCalculator(converter, evaluator); + + @DisplayName("์‚ฌ์น™์—ฐ์‚ฐ ๊ณ„์‚ฐ๊ธฐ์˜ ๊ณ„์‚ฐ์ด ์ž˜ ๋˜๋Š”์ง€ ํ™•์ธ") + @ParameterizedTest + @CsvSource({ + "1 + 5 * 3 - 8, 8", + "1 + 2 * 9 - 6, 13" + }) + void right_calculate(String inputExpression, BigDecimal expectedResult) { + + // given + Expression expression = new Expression(inputExpression); + + // when + CalculationResult result = calculator.calculate(expression); + + // then + assertThat(result.getValue()).isEqualTo(expectedResult); + + } + +} \ No newline at end of file diff --git a/src/test/java/com/programmers/calculator/domain/core/OperatorTest.java b/src/test/java/com/programmers/calculator/domain/core/OperatorTest.java new file mode 100644 index 000000000..2fd895160 --- /dev/null +++ b/src/test/java/com/programmers/calculator/domain/core/OperatorTest.java @@ -0,0 +1,57 @@ +package com.programmers.calculator.domain.core; + +import com.programmers.calculator.domain.vo.CalculationResult; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.math.BigDecimal; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +class OperatorTest { + + @DisplayName("0์œผ๋กœ ๋‚˜๋ˆด์„ ๋•Œ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋Š”์ง€ ํ™•์ธ") + @ParameterizedTest + @CsvSource({"5, 0", "13, 0"}) + void division_by_zero_throw_exception(BigDecimal testOperand1, BigDecimal testOperand2) { + + // given + Operator division = Operator.DIVISION; + + CalculationResult operand1 = new CalculationResult(testOperand1); + CalculationResult operand2 = new CalculationResult(testOperand2); + + // when + assertThatThrownBy(() -> division.getFunction() + .apply(operand1, operand2)) + .isInstanceOf(ArithmeticException.class) + .hasMessage("0์œผ๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); + + } + + @DisplayName("์—ฐ์‚ฐ์ž๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊ฒจ์ฃผ๋ฉด, ๊ทธ์— ๋งž๋Š” Operator๊ฐ€ ๋ฐ˜ํ™˜") + @ParameterizedTest + @ValueSource(chars = {'+', '-', '*', '/'}) + void operator_of_success(char operatorChar) { + + // given + Operator operator = Operator.of(operatorChar); + + // then + assertThat(operator.getSymbol()).isEqualTo(operatorChar); + } + + @DisplayName("์—ฐ์‚ฐ์ž๊ฐ€ ์•„๋‹Œ ๊ฐ’๋“ค์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊ฒจ์ฃผ๋ฉด, ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒ") + @ParameterizedTest + @ValueSource(chars = {'|', '`', 'z', '1', '3'}) + void of_throws_exception(char notOperatorChar) { + + // then + assertThatThrownBy(() -> Operator.of(notOperatorChar)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("์œ ํšจํ•˜์ง€ ์•Š์€ ์—ฐ์‚ฐ์ž์ž…๋‹ˆ๋‹ค."); + } +} \ No newline at end of file diff --git a/src/test/java/com/programmers/calculator/repository/HistoryMapRepositoryTest.java b/src/test/java/com/programmers/calculator/repository/HistoryMapRepositoryTest.java new file mode 100644 index 000000000..f96974d58 --- /dev/null +++ b/src/test/java/com/programmers/calculator/repository/HistoryMapRepositoryTest.java @@ -0,0 +1,43 @@ +package com.programmers.calculator.repository; + +import com.programmers.calculator.domain.vo.CalculationResult; +import com.programmers.calculator.domain.vo.Expression; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import java.math.BigDecimal; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class HistoryMapRepositoryTest { + + HistoryRepository repository = new HistoryMapRepository(); + + @DisplayName("repository ์ •์ƒ์ ์œผ๋กœ ์ €์žฅ๋˜๋Š”์ง€ ํ™•์ธ") + @ParameterizedTest + @CsvSource({ + "1 + 2 * 3, 7", + "3 + 2 * 3 / 3, 5" + }) + void repository_right_work(String inputExpression, BigDecimal expectedResult) { + + // given + Expression expression = new Expression(inputExpression); + CalculationResult calculationResult = new CalculationResult(expectedResult); + CalculationHistory result = new CalculationHistory(expression, calculationResult); + + // when + repository.save(result); + + // then + List all = repository.findAll(); + + assertThat(all.size()).isEqualTo(1); + assertThat(all).contains(result); + assertThat(all.get(0)).isEqualTo(result); + + } + +} \ No newline at end of file