diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 6d8ea4f..7d65ddc 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,2 +1,6 @@
# COW-SPRING 2기 학습 PR Reviewers
+<<<<<<< HEAD
* @Hoya324 @5uwhann @KoSeonJe @0702yoon @Gopistol @TaetaetaE01
+=======
+* @Hoya324 @5uwhann @KoSeonJe @0702yoon @Gopistol @TaetaetaE01
+>>>>>>> upstream/init
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..30c6bde
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,5 @@
+## 추가 Q & A
+
+🤔 잘 모르겠거나, 더 궁금한 내용이 있었나요?
+
+## 집중적으로 리뷰 받고싶은 것이 있나요?
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6c01878
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,32 @@
+HELP.md
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**
+!**/src/test/**
+
+### 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/
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..48f9b0e
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,29 @@
+plugins {
+ id("java")
+}
+
+group = "banking"
+version = "1.0-SNAPSHOT"
+
+java {
+ sourceCompatibility = '17'
+}
+
+repositories {
+ mavenCentral()
+}
+
+
+dependencies {
+ implementation 'org.projectlombok:lombok:1.18.28'
+ testImplementation(platform("org.junit:junit-bom:5.9.1"))
+ testImplementation("org.junit.jupiter:junit-jupiter")
+
+ // Lombok
+ compileOnly 'org.projectlombok:lombok:1.18.28'
+ annotationProcessor 'org.projectlombok:lombok:1.18.28'
+}
+
+tasks.test {
+ useJUnitPlatform()
+}
\ No newline at end of file
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..1aa94a4
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,249 @@
+#!/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/HEAD/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
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+
+# 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
+ if ! command -v java >/dev/null 2>&1
+ then
+ 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
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ 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
+
+
+# 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"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# 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 0000000..93e3f59
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,92 @@
+@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=.
+@rem This is normally unused
+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% equ 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% equ 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!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/issue/concurrent/Readme.md b/issue/concurrent/Readme.md
new file mode 100644
index 0000000..cb36cac
--- /dev/null
+++ b/issue/concurrent/Readme.md
@@ -0,0 +1,107 @@
+### 기존 코드
+
+```java
+package ej2;
+
+import java.math.BigDecimal;
+
+public class BigDecimalConcurrentTest {
+
+ private BigDecimal balance;
+
+ public void deposit(BigDecimal value) { // 동기화 추가
+ balance = balance.add(value);
+ }
+
+ public static void main(String[] args) {
+ BigDecimalConcurrentTest test = new BigDecimalConcurrentTest();
+ test.balance = BigDecimal.ZERO;
+
+ final int THREAD_COUNT = 1000;
+
+ Thread[] threads = new Thread[THREAD_COUNT];
+
+ // 1000개의 멀티 스레드 환경에서 각각 1씩 더하기
+ for (int i = 0; i < THREAD_COUNT; i++) {
+ threads[i] = new Thread(() -> test.deposit(new BigDecimal("1")));
+ }
+
+ for (Thread thread : threads) {
+ thread.start();
+ }
+
+ try {
+ for (Thread thread : threads) {
+ thread.join();
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ System.out.println("balance: " + test.balance);
+ }
+}
+
+```
+
+테스트를 위해 기존 코드의 deposit 메소드만 간단히 구현했습니다. (직접 테스트해보는 것을 추천합니다.)
+
+## 문제점
+
+하나의 스레드만 작동하는 `단일 스레드 환경`에서, 기존 deposit 메소드의 방식은 문제되지 않습니다.
+
+하지만, 위 코드에서 테스트한 것처럼 1000개의 스레드 (동시에 1000개의 스레드가 실행)되는 `멀티 스레드 환경`에서는 `동시성 문제`가 발생할 수 있습니다.
+
+실제로 위 코드를 실행해보면, 1000개의 스레드가 `1원` 씩 입금하는 상황에서, 실제 결과값은 우리가 생각하는 기댓값인 `1000` 보다 작은 값이 출력되는 것을 확인할 수 있습니다.
+
+
+
+
+## 동시성 문제란?
+
+한 코어(프로세스)에서 여러 개의 스레드를 이용해 번갈아 작업을 처리하는 `멀티스레드 환경` 에서, 여러 스레드가 같은 자원을 공유함으로써 하나의 자원을 두고 위와 같은 문제가 발생하는 것입니다.
+
+
+
+출처: https://dkswnkk.tistory.com/681
+
+스레드 참고 자료: https://wikidocs.net/230
+
+
+## 우리가 동시성 문제를 고민해야 하는 이유
+
+이 과제의 목표는 여러분이 `객체 지향 프로그래밍`에 익숙해지기 위하여 간단한 뱅킹 시스템을 구현해보는 데 의의를 두었습니다.
+
+하지만 여기서 한 발 더 나아가서 실제 은행 시스템을 구축한다고 생각해보면, 이용자가 많을 경우 한 계좌로 짧은 시간 안에 입금(deposit)하는 상황이 발생할 수 있고, 누군가의 `balance` 가 입금되거나 인출된 이후의 값이 기댓값과 다를 때, 큰 사고로 이어질 수 있을 것입니다.
+
+## 해결 방법
+
+먼저 `JAVA의 동시성 문제`에 대해 한 번 찾아보시고 보는 것을 권장합니다.
+
+- Solution 1 - deposit 메소드에 `synchronized` 키워드 추가
+
+ ```java
+ public synchronized void deposit(BigDecimal value) {
+ balance = balance.add(value);
+ }
+ ```
+
+- Solution 2 - `java.util.concurrent.atomic` 클래스 활용
+
+ ```java
+ private AtomicReference balance;
+
+ public void deposit(BigDecimal value) {
+ BigDecimal prevValue;
+ BigDecimal newValue;
+
+ do {
+ prevValue = balance.get(); // 현재 값 가져오기
+ newValue = prevValue.add(value); // 새 값 계산
+ } while (!balance.compareAndSet(prevValue, newValue)); // CAS 연산으로 안전하게 값 교체 시도
+
+ }
+ ```
+
+
+이외에도 동시성 문제를 제어할 수 있는 다른 방법들이 있습니다. 어떤 것들이 있는지 직접 찾아보시는 것을 추천합니다.
diff --git a/issue/concurrent/example/ProblemCase.java b/issue/concurrent/example/ProblemCase.java
new file mode 100644
index 0000000..1337801
--- /dev/null
+++ b/issue/concurrent/example/ProblemCase.java
@@ -0,0 +1,38 @@
+
+import java.math.BigDecimal;
+
+public class ProblemCase {
+
+ private BigDecimal balance;
+
+ public void deposit(BigDecimal value) {
+ balance = balance.add(value);
+ }
+
+ public static void main(String[] args) {
+ ProblemCase test = new ProblemCase();
+ test.balance = BigDecimal.ZERO;
+
+ final int THREAD_COUNT = 1000;
+
+ Thread[] threads = new Thread[THREAD_COUNT];
+
+ for (int i = 0; i < THREAD_COUNT; i++) {
+ threads[i] = new Thread(() -> test.deposit(new BigDecimal("1")));
+ }
+
+ for (Thread thread : threads) {
+ thread.start();
+ }
+
+ try {
+ for (Thread thread : threads) {
+ thread.join();
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ System.out.println("balance: " + test.balance);
+ }
+}
diff --git a/issue/concurrent/example/Solution1.java b/issue/concurrent/example/Solution1.java
new file mode 100644
index 0000000..d92d79d
--- /dev/null
+++ b/issue/concurrent/example/Solution1.java
@@ -0,0 +1,38 @@
+
+import java.math.BigDecimal;
+
+public class Solution1 {
+
+ private BigDecimal balance;
+
+ public synchronized void deposit(BigDecimal value) { // 동기화 추가
+ balance = balance.add(value);
+ }
+
+ public static void main(String[] args) {
+ Solution1 test = new Solution1();
+ test.balance = BigDecimal.ZERO;
+
+ final int THREAD_COUNT = 1000;
+
+ Thread[] threads = new Thread[THREAD_COUNT];
+
+ for (int i = 0; i < THREAD_COUNT; i++) {
+ threads[i] = new Thread(() -> test.deposit(new BigDecimal("1")));
+ }
+
+ for (Thread thread : threads) {
+ thread.start();
+ }
+
+ try {
+ for (Thread thread : threads) {
+ thread.join();
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ System.out.println("balance: " + test.balance);
+ }
+}
diff --git a/issue/concurrent/example/Solution2.java b/issue/concurrent/example/Solution2.java
new file mode 100644
index 0000000..6080132
--- /dev/null
+++ b/issue/concurrent/example/Solution2.java
@@ -0,0 +1,46 @@
+
+import java.math.BigDecimal;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class Solution2 {
+
+ private AtomicReference balance;
+
+ public void deposit(BigDecimal value) {
+ BigDecimal prevValue;
+ BigDecimal newValue;
+
+ do {
+ prevValue = balance.get(); // 현재 값 가져오기
+ newValue = prevValue.add(value); // 새 값 계산
+ } while (!balance.compareAndSet(prevValue, newValue)); // CAS 연산으로 안전하게 값 교체 시도
+
+ }
+
+ public static void main(String[] args) {
+ Solution2 test = new Solution2();
+ test.balance = new AtomicReference<>(BigDecimal.ZERO);
+
+ final int THREAD_COUNT = 1000;
+
+ Thread[] threads = new Thread[THREAD_COUNT];
+
+ for (int i = 0; i < THREAD_COUNT; i++) {
+ threads[i] = new Thread(() -> test.deposit(new BigDecimal("1")));
+ }
+
+ for (Thread thread : threads) {
+ thread.start();
+ }
+
+ try {
+ for (Thread thread : threads) {
+ thread.join();
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ System.out.println("balance: " + test.balance.get());
+ }
+}
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..c470456
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = "practice-oop-banking"
+
diff --git a/src/main/java/account/Account.java b/src/main/java/account/Account.java
new file mode 100644
index 0000000..795f926
--- /dev/null
+++ b/src/main/java/account/Account.java
@@ -0,0 +1,38 @@
+package account;
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+
+public abstract class Account {
+ protected final StringBuilder stringBuilder = new StringBuilder();
+ protected final DecimalFormat decimalFormat = new DecimalFormat("###,###");
+
+ public static Account empty(){
+ return new Account() {
+ public boolean isEmpty() { return true; }
+ public String getAccountInfo() { return null; }
+ public void withdrawal(BigDecimal value) {}
+ public void deposit(BigDecimal value) {}
+ public AccountType getAccountType() { return null; }
+ public BigDecimal getBalance() { return null; }
+ public String getAccountNumber() { return null; }
+ public boolean isDeactivate() { return false; }
+ public boolean canWithdrawal(BigDecimal withdrawalAmount) { return false; }
+ public void activate() {}
+ public void deactivate() {}
+ };
+ }
+
+ public boolean isEmpty() {
+ return false;
+ }
+ public abstract String getAccountInfo();
+ public abstract void withdrawal(BigDecimal value);
+ public abstract void deposit(BigDecimal value);
+ public abstract AccountType getAccountType();
+ public abstract BigDecimal getBalance();
+ public abstract String getAccountNumber();
+ public abstract boolean isDeactivate();
+ public abstract boolean canWithdrawal(BigDecimal withdrawalAmount);
+ public abstract void activate();
+ public abstract void deactivate();
+}
diff --git a/src/main/java/account/AccountMaker.java b/src/main/java/account/AccountMaker.java
new file mode 100644
index 0000000..5194475
--- /dev/null
+++ b/src/main/java/account/AccountMaker.java
@@ -0,0 +1,10 @@
+package account;
+
+import common.EParamType;
+import java.util.HashMap;
+import view.InputView;
+import view.OutputView;
+
+public interface AccountMaker {
+ HashMap makeAccount(InputView inputView, OutputView outputView);
+}
diff --git a/src/main/java/account/AccountType.java b/src/main/java/account/AccountType.java
new file mode 100644
index 0000000..c82eec9
--- /dev/null
+++ b/src/main/java/account/AccountType.java
@@ -0,0 +1,20 @@
+package account;
+import interest.BasicAccountInterest;
+import interest.InterestCalculator;
+import interest.SavingAccountInterest;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor
+public enum AccountType {
+ SAVING_ACCOUNT("적금 계좌", new SavingAccountMaker(), new SavingAccountInterest()),
+ BASIC_ACCOUNT("예금 계좌", new BasicAccountMaker(),new BasicAccountInterest());
+
+ public static final int MIN_INDEX = 1;
+ public static final int MAX_INDEX = AccountType.values().length;
+
+ private final String accountName;
+ private final AccountMaker accountMaker;
+ private final InterestCalculator interestCalculator;
+}
diff --git a/src/main/java/account/BasicAccount.java b/src/main/java/account/BasicAccount.java
new file mode 100644
index 0000000..2d99fd9
--- /dev/null
+++ b/src/main/java/account/BasicAccount.java
@@ -0,0 +1,64 @@
+package account;
+import java.math.BigDecimal;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+@Builder
+@AllArgsConstructor(access = AccessLevel.PUBLIC)
+public class BasicAccount extends Account {
+ private final AccountType accountType = AccountType.BASIC_ACCOUNT;
+ private final String accountNumber;
+ private String owner;
+ private BigDecimal balance;
+ private boolean activation;
+
+ @Override
+ public String getAccountNumber() {
+ return this.accountNumber;
+ }
+
+ @Override
+ public synchronized void deactivate() {
+ this.activation = false;
+ }
+
+ @Override
+ public boolean canWithdrawal(BigDecimal withdrawalAmount) {
+ return (balance.compareTo(withdrawalAmount) >= 0);
+ }
+
+ @Override
+ public synchronized void activate() {
+ this.activation = true;
+ }
+
+ @Override
+ public boolean isDeactivate() {
+ return !this.activation;
+ }
+
+ @Override
+ public synchronized void withdrawal(BigDecimal value) {
+ this.balance = this.balance.subtract(value);
+ }
+
+ @Override
+ public synchronized void deposit(BigDecimal value) {
+ this.balance = this.balance.add(value);
+ }
+
+ public synchronized String getAccountInfo() {
+ stringBuilder.setLength(0);
+ stringBuilder.append("[계좌 정보]").append("\n");
+ stringBuilder.append("계좌 종류: ").append(accountType.getAccountName()).append("\n");
+ stringBuilder.append("계좌번호: ").append(getAccountNumber()).append("\n");
+ stringBuilder.append("계좌주: ").append(getOwner()).append("\n");
+ stringBuilder.append("잔액: ₩").append(decimalFormat.format(getBalance())).append("\n");
+ stringBuilder.append("활성화 상태: ").append(isActivation());
+ return stringBuilder.toString();
+ }
+}
diff --git a/src/main/java/account/BasicAccountMaker.java b/src/main/java/account/BasicAccountMaker.java
new file mode 100644
index 0000000..f84557d
--- /dev/null
+++ b/src/main/java/account/BasicAccountMaker.java
@@ -0,0 +1,24 @@
+package account;
+
+import common.EParamType;
+import common.RequireMessage;
+import java.util.HashMap;
+import view.InputView;
+import view.OutputView;
+
+public class BasicAccountMaker implements AccountMaker {
+
+ @Override
+ public HashMap makeAccount(InputView inputView, OutputView outputView) throws NumberFormatException{
+ HashMap accountInfo = new HashMap<>();
+ AccountType accountType = AccountType.BASIC_ACCOUNT;
+ outputView.print(RequireMessage.RequireOwner);
+ String owner = inputView.getString();
+ outputView.print(RequireMessage.RequireActivation);
+ boolean activation = inputView.getBoolean();
+ accountInfo.put(EParamType.eAccountType, accountType);
+ accountInfo.put(EParamType.eOwner, owner);
+ accountInfo.put(EParamType.eActivation, activation);
+ return accountInfo;
+ }
+}
diff --git a/src/main/java/account/SavingAccount.java b/src/main/java/account/SavingAccount.java
new file mode 100644
index 0000000..2e3d341
--- /dev/null
+++ b/src/main/java/account/SavingAccount.java
@@ -0,0 +1,67 @@
+package account;
+import java.math.BigDecimal;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+@Builder
+@AllArgsConstructor(access = AccessLevel.PUBLIC)
+public class SavingAccount extends Account {
+ private final AccountType accountType = AccountType.SAVING_ACCOUNT;
+ private final String accountNumber;
+ private String owner;
+ private BigDecimal balance;
+ private BigDecimal targetAmount;
+ private boolean activation;
+
+ @Override
+ public String getAccountNumber() {
+ return this.accountNumber;
+ }
+
+ @Override
+ public boolean canWithdrawal(BigDecimal withdrawalAmount) {
+ return (balance.compareTo(withdrawalAmount) >= 0 && balance.compareTo(targetAmount) >= 0);
+ }
+
+ @Override
+ public synchronized void deactivate() {
+ this.activation = false;
+ }
+
+ @Override
+ public synchronized void activate() {
+ this.activation = true;
+ }
+
+ @Override
+ public boolean isDeactivate() {
+ return !this.activation;
+ }
+
+ @Override
+ public synchronized void withdrawal(BigDecimal value) {
+ this.balance = this.balance.subtract(value);
+ }
+
+ @Override
+ public synchronized void deposit(BigDecimal value) {
+ this.balance = this.balance.add(value);
+ }
+
+ @Override
+ public synchronized String getAccountInfo() {
+ stringBuilder.setLength(0);
+ stringBuilder.append("[계좌 정보]").append("\n");
+ stringBuilder.append("계좌 종류: ").append(accountType.getAccountName()).append("\n");
+ stringBuilder.append("계좌번호: ").append(getAccountNumber()).append("\n");
+ stringBuilder.append("계좌주: ").append(getOwner()).append("\n");
+ stringBuilder.append("잔액: ₩").append(decimalFormat.format(getBalance())).append("\n");
+ stringBuilder.append("목표 금액: ₩").append(decimalFormat.format(getTargetAmount())).append("\n");
+ stringBuilder.append("활성화 상태: ").append(isActivation());
+ return stringBuilder.toString();
+ }
+}
diff --git a/src/main/java/account/SavingAccountMaker.java b/src/main/java/account/SavingAccountMaker.java
new file mode 100644
index 0000000..3a9e3a2
--- /dev/null
+++ b/src/main/java/account/SavingAccountMaker.java
@@ -0,0 +1,30 @@
+package account;
+
+import common.EParamType;
+import common.Message;
+import common.RequireMessage;
+import java.math.BigDecimal;
+import java.util.HashMap;
+import view.InputView;
+import view.OutputView;
+
+public class SavingAccountMaker implements AccountMaker {
+
+ @Override
+ public HashMap makeAccount(InputView inputView, OutputView outputView) throws NumberFormatException{
+ HashMap accountInfo = new HashMap<>();
+ AccountType accountType = AccountType.SAVING_ACCOUNT;
+ outputView.print(RequireMessage.RequireOwner);
+ String owner = inputView.getString();
+ outputView.print(RequireMessage.RequireActivation);
+ boolean activation = inputView.getBoolean();
+ outputView.print(Message.Target.getPrintMessage() + " " +
+ RequireMessage.RequireAmount.getPrintMessage());
+ BigDecimal targetAmount = inputView.getBigDecimal();
+ accountInfo.put(EParamType.eAccountType, accountType);
+ accountInfo.put(EParamType.eOwner, owner);
+ accountInfo.put(EParamType.eActivation, activation);
+ accountInfo.put(EParamType.eTargetAmount, targetAmount);
+ return accountInfo;
+ }
+}
diff --git a/src/main/java/application/AppConfig.java b/src/main/java/application/AppConfig.java
new file mode 100644
index 0000000..057211a
--- /dev/null
+++ b/src/main/java/application/AppConfig.java
@@ -0,0 +1,29 @@
+package application;
+
+import view.BankingView;
+import view.InputView;
+import view.InputViewImpl;
+import view.OutputView;
+import view.OutputViewImpl;
+
+public class AppConfig {
+
+ public BankingView banking() {
+ return new BankingView(bankingManager(), inputView(), outputView());
+ }
+ private BankingManager bankingManager() {
+ return new BankingManager(frontController());
+ }
+
+ private FrontController frontController() {
+ return new FrontController();
+ }
+
+ private InputView inputView() {
+ return InputViewImpl.getInstance();
+ }
+
+ private OutputView outputView() {
+ return OutputViewImpl.getInstance();
+ }
+}
diff --git a/src/main/java/application/Application.java b/src/main/java/application/Application.java
new file mode 100644
index 0000000..914e2ce
--- /dev/null
+++ b/src/main/java/application/Application.java
@@ -0,0 +1,14 @@
+package application;
+
+import view.BankingView;
+
+public class Application {
+ public static void main(String[] args) {
+ AppConfig config = new AppConfig();
+ BankingView banking = config.banking();
+ boolean isRunning;
+ do {
+ isRunning = banking.run();
+ } while (isRunning);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/application/BankingManager.java b/src/main/java/application/BankingManager.java
new file mode 100644
index 0000000..6e21e00
--- /dev/null
+++ b/src/main/java/application/BankingManager.java
@@ -0,0 +1,91 @@
+package application;
+
+import common.EParamType;
+import common.Request;
+import common.Response;
+import exception.DeactivatedAccountException;
+import exception.NotExistAccountException;
+import java.math.BigDecimal;
+import java.util.HashMap;
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
+import view.EMenu;
+
+@RequiredArgsConstructor(access = AccessLevel.PUBLIC)
+public class BankingManager {
+ private final FrontController frontController;
+
+ public Response makeAccount(HashMap params)
+ throws NotExistAccountException, DeactivatedAccountException {
+ Request request = new Request(EMenu.eMakeAccount, params);
+ return frontController.process(request);
+ }
+
+ public Response deleteAccount(String accountNumber)
+ throws NotExistAccountException, DeactivatedAccountException {
+ HashMap params = new HashMap<>();
+ params.put(EParamType.eAccountNumber, accountNumber);
+ Request request = new Request(EMenu.eDeleteAccount, params);
+ return frontController.process(request);
+ }
+
+ public Response activateAccount(String accountNumber)
+ throws NotExistAccountException, DeactivatedAccountException {
+ HashMap params = new HashMap<>();
+ params.put(EParamType.eAccountNumber, accountNumber);
+ Request request = new Request(EMenu.eActivateAccount, params);
+ return frontController.process(request);
+ }
+
+ public Response deactivateAccount(String accountNumber)
+ throws NotExistAccountException, DeactivatedAccountException {
+ HashMap params = new HashMap<>();
+ params.put(EParamType.eAccountNumber, accountNumber);
+ Request request = new Request(EMenu.eDeactivateAccount, params);
+ return frontController.process(request);
+ }
+
+ public Response getAccount(String accountNumber)
+ throws NotExistAccountException, DeactivatedAccountException {
+ HashMap params = new HashMap<>();
+ params.put(EParamType.eAccountNumber, accountNumber);
+ Request request = new Request(EMenu.eAccountInfo, params);
+ return frontController.process(request);
+ }
+
+ public Response getInterest(String accountNumber)
+ throws NotExistAccountException, DeactivatedAccountException {
+ HashMap params = new HashMap<>();
+ params.put(EParamType.eAccountNumber, accountNumber);
+ Request request = new Request(EMenu.eInterest, params);
+ return frontController.process(request);
+ }
+
+ public Response remittance(String withDrawalAccountNumber, String depositAccountNumber, BigDecimal amount)
+ throws NotExistAccountException, DeactivatedAccountException {
+ HashMap params = new HashMap<>();
+ params.put(EParamType.eWithdrawalAccountNumber, withDrawalAccountNumber);
+ params.put(EParamType.eDepositAccountNumber, depositAccountNumber);
+ params.put(EParamType.eAmount, amount);
+ Request request = new Request(EMenu.eRemittance, params);
+ return frontController.process(request);
+ }
+
+ public Response deposit(String accountNumber, BigDecimal depositAmount)
+ throws NotExistAccountException, DeactivatedAccountException {
+ HashMap params = new HashMap<>();
+ params.put(EParamType.eAccountNumber, accountNumber);
+ params.put(EParamType.eAmount, depositAmount);
+ Request request = new Request(EMenu.eDeposit, params);
+ return frontController.process(request);
+ }
+
+ public Response withdrawal(String accountNumber, BigDecimal withdrawalAmount)
+ throws NotExistAccountException, DeactivatedAccountException {
+ HashMap params = new HashMap<>();
+ params.put(EParamType.eAccountNumber, accountNumber);
+ params.put(EParamType.eAmount, withdrawalAmount);
+ Request request = new Request(EMenu.eWithdrawal, params);
+ return frontController.process(request);
+ }
+}
diff --git a/src/main/java/application/FrontController.java b/src/main/java/application/FrontController.java
new file mode 100644
index 0000000..2d43cd6
--- /dev/null
+++ b/src/main/java/application/FrontController.java
@@ -0,0 +1,39 @@
+package application;
+
+import controller.ActivateAccountController;
+import controller.BankingController;
+import controller.DeactivateAccountController;
+import controller.DeleteAccountController;
+import controller.DepositController;
+import controller.GetAccountInfoController;
+import controller.GetInterestController;
+import controller.CreateAccountController;
+import controller.RemittanceController;
+import controller.WithdrawalController;
+import common.Request;
+import common.Response;
+import exception.DeactivatedAccountException;
+import exception.NotExistAccountException;
+import java.util.HashMap;
+import view.EMenu;
+
+public class FrontController {
+ private final HashMap controllerMap = new HashMap<>();
+
+ public FrontController() {
+ controllerMap.put(EMenu.eMakeAccount, new CreateAccountController());
+ controllerMap.put(EMenu.eDeleteAccount, new DeleteAccountController());
+ controllerMap.put(EMenu.eAccountInfo, new GetAccountInfoController());
+ controllerMap.put(EMenu.eActivateAccount, new ActivateAccountController());
+ controllerMap.put(EMenu.eDeactivateAccount, new DeactivateAccountController());
+ controllerMap.put(EMenu.eWithdrawal, new WithdrawalController());
+ controllerMap.put(EMenu.eDeposit, new DepositController());
+ controllerMap.put(EMenu.eRemittance, new RemittanceController());
+ controllerMap.put(EMenu.eInterest, new GetInterestController());
+ }
+
+ public Response process(Request request)
+ throws NotExistAccountException, DeactivatedAccountException {
+ return controllerMap.get(request.getRequest()).process(request);
+ }
+}
diff --git a/src/main/java/common/EParamType.java b/src/main/java/common/EParamType.java
new file mode 100644
index 0000000..2c2778b
--- /dev/null
+++ b/src/main/java/common/EParamType.java
@@ -0,0 +1,12 @@
+package common;
+
+public enum EParamType {
+ eAccountNumber,
+ eAccountType,
+ eOwner,
+ eActivation,
+ eTargetAmount,
+ eWithdrawalAccountNumber,
+ eDepositAccountNumber,
+ eAmount
+}
diff --git a/src/main/java/common/ErrorMessage.java b/src/main/java/common/ErrorMessage.java
new file mode 100644
index 0000000..26ae1ea
--- /dev/null
+++ b/src/main/java/common/ErrorMessage.java
@@ -0,0 +1,20 @@
+package common;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+public enum ErrorMessage {
+ NumberFormat("숫자를 입력해주세요."),
+ NegativeNumber("0보다 큰 숫자를 입력해주세요."),
+ NotExistAccount("존재하지 않는 계좌입니다."),
+ IndexOutOfRange("숫자가 범위를 초과하였습니다."),
+ MaxAccountCount("계좌를 더 이상 만들 수 없습니다."),
+ AccountTypeException("잘못된 계좌 종류입니다."),
+ DeactivatedAccount("비활성화된 계좌입니다."),
+ NotEnoughBalanceException("현재 잔액으로는 송금할 수 없는 금액입니다. 잔액 또는 목표금액을 확인해주세요.");
+
+ private final String printMessage;
+}
diff --git a/src/main/java/common/Message.java b/src/main/java/common/Message.java
new file mode 100644
index 0000000..747cb9f
--- /dev/null
+++ b/src/main/java/common/Message.java
@@ -0,0 +1,44 @@
+package common;
+
+import view.EMenu;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+public enum Message {
+ SELECT_MENU(makeMenu()),
+ Exit("프로그램이 종료되었습니다."),
+ AccountCreationFailed("계좌 생성에 실패하였습니다."),
+ Withdrawal("출금"),
+ Deposit("입금"),
+ Remittance("송금"),
+ Delete("삭제"),
+ Activate("활성화"),
+ Deactivate("비활성화"),
+ Amount("금액"),
+ Target("목표"),
+ Complete("성공하였습니다."),
+ Balance("잔액"),
+ Interest("이자"),
+ NotExistAccount("계좌가 존재하지 않습니다."),
+ DeactivatedAccount("계좌가 비활성화 상태입니다,"),
+ NegativeNumber("0보다 큰 숫자를 입력해주세요."),
+ NotEnoughBalanceException("잔액이 충분하지 않습니다."),
+ IndexOutOfRange("범위를 벗어난 숫자입니다.");
+
+ private final String printMessage;
+
+ private static String makeMenu() {
+ StringBuilder menuBuilder = new StringBuilder();
+ menuBuilder.append("[메뉴 선택]");
+ for(EMenu eMenu : EMenu.values()){
+ menuBuilder.append("\n")
+ .append(eMenu.ordinal() + EMenu.MIN_INDEX)
+ .append(". ")
+ .append(eMenu.getMenuText());
+ }
+ return menuBuilder.toString();
+ }
+}
diff --git a/src/main/java/common/Request.java b/src/main/java/common/Request.java
new file mode 100644
index 0000000..4aa1c93
--- /dev/null
+++ b/src/main/java/common/Request.java
@@ -0,0 +1,18 @@
+package common;
+
+import java.util.HashMap;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import view.EMenu;
+
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PUBLIC)
+public class Request {
+ private final EMenu request;
+ private final HashMap params;
+
+ public Object getParam(EParamType paramType){
+ return params.get(paramType);
+ }
+}
diff --git a/src/main/java/common/RequireMessage.java b/src/main/java/common/RequireMessage.java
new file mode 100644
index 0000000..829e71e
--- /dev/null
+++ b/src/main/java/common/RequireMessage.java
@@ -0,0 +1,30 @@
+package common;
+
+import account.AccountType;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+public enum RequireMessage {
+ RequireAccountType(accountTypeMessage()),
+ RequireOwner("계좌주의 이름을 입력해주세요."),
+ RequireActivation("계좌 활성화 상태를 입력해주세요. ( 0 : 비활성화 / 1 : 활성화)"),
+ RequireAmount("0 보다 큰 금액을 입력해주세요."),
+ RequireAccountNumber("계좌번호를 입력해주세요.");
+
+ private final String printMessage;
+
+ private static String accountTypeMessage(){
+ StringBuilder accountTypeBuilder = new StringBuilder();
+ accountTypeBuilder.append("[계좌 종류 선택]");
+ for(AccountType accountType : AccountType.values()){
+ accountTypeBuilder.append("\n")
+ .append(accountType.ordinal() + AccountType.MIN_INDEX)
+ .append(". ")
+ .append(accountType.getAccountName());
+ }
+ return accountTypeBuilder.toString();
+ }
+}
diff --git a/src/main/java/common/Response.java b/src/main/java/common/Response.java
new file mode 100644
index 0000000..3039104
--- /dev/null
+++ b/src/main/java/common/Response.java
@@ -0,0 +1,11 @@
+package common;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PUBLIC)
+public class Response {
+ private final Object message;
+}
diff --git a/src/main/java/controller/ActivateAccountController.java b/src/main/java/controller/ActivateAccountController.java
new file mode 100644
index 0000000..6f2cc1a
--- /dev/null
+++ b/src/main/java/controller/ActivateAccountController.java
@@ -0,0 +1,30 @@
+package controller;
+
+import account.Account;
+import common.EParamType;
+import common.Message;
+import common.Request;
+import common.Response;
+import exception.NotExistAccountException;
+import java.util.Optional;
+import service.AccountService;
+import service.AccountServiceImpl;
+
+public class ActivateAccountController implements BankingController {
+ AccountService accountService = AccountServiceImpl.getInstance();
+
+ @Override
+ public Response process(Request request) throws NotExistAccountException {
+ return activateAccount(request);
+ }
+
+ public Response activateAccount(Request request)
+ throws NotExistAccountException {
+ String accountNumber = (String) request.getParam(EParamType.eAccountNumber);
+ Account account = accountService.getAccount(accountNumber);
+ Optional.ofNullable(account).orElseThrow(()
+ -> new NotExistAccountException(Message.NotExistAccount, accountNumber));
+ accountService.activate(account);
+ return new Response(Message.Complete);
+ }
+}
diff --git a/src/main/java/controller/BankingController.java b/src/main/java/controller/BankingController.java
new file mode 100644
index 0000000..35eb9d7
--- /dev/null
+++ b/src/main/java/controller/BankingController.java
@@ -0,0 +1,10 @@
+package controller;
+
+import common.Request;
+import common.Response;
+import exception.DeactivatedAccountException;
+import exception.NotExistAccountException;
+
+public interface BankingController {
+ Response process(Request request) throws NotExistAccountException, DeactivatedAccountException;
+}
diff --git a/src/main/java/controller/CreateAccountController.java b/src/main/java/controller/CreateAccountController.java
new file mode 100644
index 0000000..b67b9f8
--- /dev/null
+++ b/src/main/java/controller/CreateAccountController.java
@@ -0,0 +1,83 @@
+package controller;
+
+import account.Account;
+import account.AccountType;
+import account.BasicAccount;
+import account.SavingAccount;
+import common.EParamType;
+import common.Request;
+import common.Response;
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.concurrent.ThreadLocalRandom;
+import service.AccountService;
+import service.AccountServiceImpl;
+
+public class CreateAccountController implements BankingController {
+ private static final int TRY_CREATE_MAX_COUNT = 10;
+ private static final int MIN_ACCOUNT_NUMBER = 100000000;
+ private static final int MAX_ACCOUNT_NUMBER = 999999999;
+ AccountService accountService = AccountServiceImpl.getInstance();
+
+ @Override
+ public Response process(Request request) {
+ HashMap params = request.getParams();
+ AccountType accountType = (AccountType) params.get(EParamType.eAccountType);
+ return switch(accountType){
+ case SAVING_ACCOUNT -> makeSavingAccount(params);
+ case BASIC_ACCOUNT -> makeBasicAccount(params);
+ };
+ }
+
+ private String makeAccountNumber() {
+ int tryCount = 0;
+ String accountNumber;
+ while (tryCount < TRY_CREATE_MAX_COUNT) {
+ accountNumber
+ = ThreadLocalRandom.current().nextInt(MIN_ACCOUNT_NUMBER, MAX_ACCOUNT_NUMBER) + "";
+ if (!accountService.checkNumber(accountNumber)) {
+ return accountNumber;
+ }
+ tryCount++;
+ }
+ return null;
+ }
+
+ private Response makeSavingAccount(HashMap params){
+ String accountNumber = makeAccountNumber();
+ if (accountNumber == null) {
+ return new Response(Account.empty());
+ }
+ String owner = (String) params.get(EParamType.eOwner);
+ boolean activation = (boolean) params.get(EParamType.eActivation);
+ BigDecimal targetAmount = (BigDecimal) params.get(EParamType.eTargetAmount);
+ BigDecimal balance = new BigDecimal(0);
+ Account account = SavingAccount.builder()
+ .accountNumber(accountNumber)
+ .owner(owner)
+ .balance(balance)
+ .targetAmount(targetAmount)
+ .activation(activation)
+ .build();
+ accountService.addAccount(account);
+ return new Response(account);
+ }
+
+ private Response makeBasicAccount(HashMap params){
+ String accountNumber = makeAccountNumber();
+ if (accountNumber == null) {
+ return new Response(Account.empty());
+ }
+ String owner = (String) params.get(EParamType.eOwner);
+ boolean activation = (boolean) params.get(EParamType.eActivation);
+ BigDecimal balance = new BigDecimal(0);
+ Account account = BasicAccount.builder()
+ .accountNumber(accountNumber)
+ .owner(owner)
+ .balance(balance)
+ .activation(activation)
+ .build();
+ accountService.addAccount(account);
+ return new Response(account);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/controller/DeactivateAccountController.java b/src/main/java/controller/DeactivateAccountController.java
new file mode 100644
index 0000000..6d3ad6b
--- /dev/null
+++ b/src/main/java/controller/DeactivateAccountController.java
@@ -0,0 +1,36 @@
+package controller;
+
+import account.Account;
+import common.EParamType;
+import common.Message;
+import common.Request;
+import common.Response;
+import exception.DeactivatedAccountException;
+import exception.NotExistAccountException;
+import java.util.Optional;
+import service.AccountService;
+import service.AccountServiceImpl;
+
+public class DeactivateAccountController implements BankingController {
+ AccountService accountService = AccountServiceImpl.getInstance();
+
+ @Override
+ public Response process(Request request)
+ throws NotExistAccountException, DeactivatedAccountException {
+ return deactivate(request);
+ }
+
+ private Response deactivate(Request request)
+ throws NotExistAccountException, DeactivatedAccountException{
+ String accountNumber = (String) request.getParam(EParamType.eAccountNumber);
+ Account account = accountService.getAccount(accountNumber);
+ Optional.ofNullable(account)
+ .orElseThrow(
+ () -> new NotExistAccountException(Message.NotExistAccount, accountNumber));
+ if (account.isDeactivate()){
+ throw new DeactivatedAccountException(Message.DeactivatedAccount, accountNumber);
+ }
+ accountService.deactivate(account);
+ return new Response(Message.Complete);
+ }
+}
diff --git a/src/main/java/controller/DeleteAccountController.java b/src/main/java/controller/DeleteAccountController.java
new file mode 100644
index 0000000..e729d93
--- /dev/null
+++ b/src/main/java/controller/DeleteAccountController.java
@@ -0,0 +1,29 @@
+package controller;
+
+import account.Account;
+import common.EParamType;
+import common.Message;
+import common.Request;
+import common.Response;
+import exception.NotExistAccountException;
+import java.util.Optional;
+import service.AccountService;
+import service.AccountServiceImpl;
+
+public class DeleteAccountController implements BankingController {
+ AccountService accountService = AccountServiceImpl.getInstance();
+
+ @Override
+ public Response process(Request request) throws NotExistAccountException{
+ return deleteAccount(request);
+ }
+
+ private Response deleteAccount(Request request) throws NotExistAccountException {
+ String accountNumber = (String) request.getParam(EParamType.eAccountNumber);
+ Account account = accountService.getAccount(accountNumber);
+ Optional.ofNullable(account).orElseThrow(()
+ -> new NotExistAccountException(Message.NotExistAccount, accountNumber));
+ accountService.deleteAccount(account);
+ return new Response(Message.Complete);
+ }
+}
diff --git a/src/main/java/controller/DepositController.java b/src/main/java/controller/DepositController.java
new file mode 100644
index 0000000..b923be7
--- /dev/null
+++ b/src/main/java/controller/DepositController.java
@@ -0,0 +1,42 @@
+package controller;
+
+import account.Account;
+import common.EParamType;
+import common.Message;
+import common.Request;
+import common.Response;
+import exception.DeactivatedAccountException;
+import exception.NotExistAccountException;
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Optional;
+import service.AccountService;
+import service.AccountServiceImpl;
+
+public class DepositController implements BankingController {
+ AccountService accountService = AccountServiceImpl.getInstance();
+
+ @Override
+ public Response process(Request request)
+ throws NotExistAccountException, DeactivatedAccountException {
+ return deposit(request);
+ }
+
+ private Response deposit(Request request)
+ throws NotExistAccountException, DeactivatedAccountException {
+ HashMap params = request.getParams();
+ String accountNumber = (String) params.get(EParamType.eAccountNumber);
+ BigDecimal amount = (BigDecimal) params.get(EParamType.eAmount);
+ Account account = accountService.getAccount(accountNumber);
+ Optional.ofNullable(account).orElseThrow(()
+ -> new NotExistAccountException(Message.NotExistAccount, accountNumber));
+ if (account.isDeactivate()){
+ throw new DeactivatedAccountException(Message.DeactivatedAccount, accountNumber);
+ }
+ if (amount.compareTo(BigDecimal.ZERO) < 0){
+ return new Response(Message.NegativeNumber);
+ }
+ accountService.deposit(account, amount);
+ return new Response(Message.Complete);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/controller/GetAccountInfoController.java b/src/main/java/controller/GetAccountInfoController.java
new file mode 100644
index 0000000..9ac5ea5
--- /dev/null
+++ b/src/main/java/controller/GetAccountInfoController.java
@@ -0,0 +1,28 @@
+package controller;
+
+import account.Account;
+import common.EParamType;
+import common.Message;
+import common.Request;
+import common.Response;
+import exception.NotExistAccountException;
+import java.util.Optional;
+import service.AccountService;
+import service.AccountServiceImpl;
+
+public class GetAccountInfoController implements BankingController {
+ AccountService accountService = AccountServiceImpl.getInstance();
+
+ @Override
+ public Response process(Request request) throws NotExistAccountException {
+ return getAccountInfo(request);
+ }
+
+ public Response getAccountInfo(Request request) throws NotExistAccountException {
+ String accountNumber = (String) request.getParam(EParamType.eAccountNumber);
+ Account account = accountService.getAccount(accountNumber);
+ return new Response(
+ Optional.ofNullable(account).orElseThrow(()
+ -> new NotExistAccountException(Message.NotExistAccount, accountNumber)));
+ }
+}
diff --git a/src/main/java/controller/GetInterestController.java b/src/main/java/controller/GetInterestController.java
new file mode 100644
index 0000000..349d924
--- /dev/null
+++ b/src/main/java/controller/GetInterestController.java
@@ -0,0 +1,35 @@
+package controller;
+
+import account.Account;
+import common.EParamType;
+import common.Message;
+import common.Request;
+import common.Response;
+import exception.DeactivatedAccountException;
+import exception.NotExistAccountException;
+import java.math.BigDecimal;
+import service.AccountService;
+import service.AccountServiceImpl;
+import service.InterestService;
+import service.InterestServiceImpl;
+
+public class GetInterestController implements BankingController {
+ AccountService accountService = AccountServiceImpl.getInstance();
+ InterestService interestService = InterestServiceImpl.getInstance();
+
+ @Override
+ public Response process(Request request)
+ throws NotExistAccountException, DeactivatedAccountException {
+ return getInterest(request);
+ }
+
+ private Response getInterest(Request request) throws DeactivatedAccountException{
+ String accountNumber = (String) request.getParam(EParamType.eAccountNumber);
+ Account account = accountService.getAccount(accountNumber);
+ if (account.isDeactivate()){
+ throw new DeactivatedAccountException(Message.DeactivatedAccount, accountNumber);
+ }
+ BigDecimal interest = interestService.getInterest(account.getAccountType(), account.getBalance());
+ return new Response(interest);
+ }
+}
diff --git a/src/main/java/controller/RemittanceController.java b/src/main/java/controller/RemittanceController.java
new file mode 100644
index 0000000..7199e3c
--- /dev/null
+++ b/src/main/java/controller/RemittanceController.java
@@ -0,0 +1,54 @@
+package controller;
+
+import account.Account;
+import common.EParamType;
+import common.Message;
+import common.Request;
+import common.Response;
+import exception.DeactivatedAccountException;
+import exception.NotExistAccountException;
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Optional;
+import service.AccountService;
+import service.AccountServiceImpl;
+
+public class RemittanceController implements BankingController {
+ AccountService accountService = AccountServiceImpl.getInstance();
+
+ @Override
+ public Response process(Request request)
+ throws NotExistAccountException, DeactivatedAccountException {
+ return remittance(request);
+ }
+
+ private Account checkAccount(String accountNumber)
+ throws NotExistAccountException, DeactivatedAccountException {
+ Account account = accountService.getAccount(accountNumber);
+ Optional.ofNullable(account).orElseThrow( ()
+ -> new NotExistAccountException(Message.NotExistAccount, accountNumber));
+ if (account.isDeactivate()){
+ throw new DeactivatedAccountException(Message.DeactivatedAccount, accountNumber);
+ }
+ return account;
+ }
+
+ private Response remittance(Request request)
+ throws NotExistAccountException, DeactivatedAccountException{
+ HashMap params = request.getParams();
+ BigDecimal amount = (BigDecimal) params.get(EParamType.eAmount);
+ String withdrawalAccountNumber = (String) params.get(EParamType.eWithdrawalAccountNumber);
+ String depositAccountNumber = (String) params.get(EParamType.eDepositAccountNumber);
+ Account withdrawalAccount = checkAccount(withdrawalAccountNumber);
+ Account depositAccount = checkAccount(depositAccountNumber);
+ if (amount.compareTo(BigDecimal.ZERO) < 0){
+ return new Response(Message.NegativeNumber);
+ }
+ if (!withdrawalAccount.canWithdrawal(amount)){
+ return new Response(Message.NotEnoughBalanceException);
+ }
+ accountService.withdrawal(withdrawalAccount, amount);
+ accountService.deposit(depositAccount, amount);
+ return new Response(Message.Complete);
+ }
+}
diff --git a/src/main/java/controller/WithdrawalController.java b/src/main/java/controller/WithdrawalController.java
new file mode 100644
index 0000000..2d83ed3
--- /dev/null
+++ b/src/main/java/controller/WithdrawalController.java
@@ -0,0 +1,45 @@
+package controller;
+
+import account.Account;
+import common.EParamType;
+import common.Message;
+import common.Request;
+import common.Response;
+import exception.DeactivatedAccountException;
+import exception.NotExistAccountException;
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Optional;
+import service.AccountService;
+import service.AccountServiceImpl;
+
+public class WithdrawalController implements BankingController {
+ AccountService accountService = AccountServiceImpl.getInstance();
+
+ @Override
+ public Response process(Request request)
+ throws NotExistAccountException, DeactivatedAccountException {
+ return withdrawal(request);
+ }
+
+ private Response withdrawal(Request request)
+ throws NotExistAccountException, DeactivatedAccountException {
+ HashMap params = request.getParams();
+ String accountNumber = (String) params.get(EParamType.eAccountNumber);
+ BigDecimal amount = (BigDecimal) params.get(EParamType.eAmount);
+ Account account = accountService.getAccount(accountNumber);
+ Optional.ofNullable(account).orElseThrow(()
+ -> new NotExistAccountException(Message.NotExistAccount, accountNumber));
+ if (account.isDeactivate()){
+ throw new DeactivatedAccountException(Message.DeactivatedAccount, accountNumber);
+ }
+ if (amount.compareTo(BigDecimal.ZERO) < 0){
+ return new Response(Message.NegativeNumber);
+ }
+ if (!account.canWithdrawal(amount)){
+ return new Response(Message.NotEnoughBalanceException);
+ }
+ accountService.withdrawal(account, amount);
+ return new Response(Message.Complete);
+ }
+}
diff --git a/src/main/java/exception/DeactivatedAccountException.java b/src/main/java/exception/DeactivatedAccountException.java
new file mode 100644
index 0000000..10ee47d
--- /dev/null
+++ b/src/main/java/exception/DeactivatedAccountException.java
@@ -0,0 +1,14 @@
+package exception;
+
+import common.Message;
+import lombok.Getter;
+
+@Getter
+public class DeactivatedAccountException extends Exception {
+
+ private final String accountNumber;
+ public DeactivatedAccountException(Message errorMessage, String accountNumber) {
+ super(errorMessage.getPrintMessage());
+ this.accountNumber = accountNumber;
+ }
+}
diff --git a/src/main/java/exception/IndexOutOfRangeException.java b/src/main/java/exception/IndexOutOfRangeException.java
new file mode 100644
index 0000000..1cd0a7f
--- /dev/null
+++ b/src/main/java/exception/IndexOutOfRangeException.java
@@ -0,0 +1,18 @@
+package exception;
+
+import common.Message;
+import lombok.Getter;
+
+@Getter
+public class IndexOutOfRangeException extends Exception{
+ private final int minIndex;
+ private final int maxIndex;
+ private final int inputIndex;
+
+ public IndexOutOfRangeException(Message errorMessage, int minIndex, int maxIndex, int inputIndex) {
+ super(errorMessage.getPrintMessage());
+ this.minIndex = minIndex;
+ this.maxIndex = maxIndex;
+ this.inputIndex = inputIndex;
+ }
+}
diff --git a/src/main/java/exception/NotExistAccountException.java b/src/main/java/exception/NotExistAccountException.java
new file mode 100644
index 0000000..0d89139
--- /dev/null
+++ b/src/main/java/exception/NotExistAccountException.java
@@ -0,0 +1,18 @@
+package exception;
+
+import common.Message;
+import lombok.Getter;
+
+@Getter
+public class NotExistAccountException extends Exception {
+ private final String accountNumber;
+ public NotExistAccountException(String errorMessage, String accountNumber) {
+ super(errorMessage);
+ this.accountNumber = accountNumber;
+ }
+
+ public NotExistAccountException(Message errorMessage, String accountNumber) {
+ super(errorMessage.getPrintMessage());
+ this.accountNumber = accountNumber;
+ }
+}
diff --git a/src/main/java/interest/BasicAccountInterest.java b/src/main/java/interest/BasicAccountInterest.java
new file mode 100644
index 0000000..a77c776
--- /dev/null
+++ b/src/main/java/interest/BasicAccountInterest.java
@@ -0,0 +1,21 @@
+package interest;
+import java.math.BigDecimal;
+
+public class BasicAccountInterest implements InterestCalculator {
+ @Override
+ public BigDecimal getInterest(BigDecimal balance) {
+ BigDecimal interest;
+ if (balance.compareTo(BasicAccountInterestRange.INTEREST_RANGE_1.getValue()) >= 0) {
+ interest = balance.multiply(BasicAccountInterestRange.INTEREST_PERCENT_1.getValue());
+ } else if (balance.compareTo(BasicAccountInterestRange.INTEREST_RANGE_2.getValue()) >= 0) {
+ interest = balance.multiply(BasicAccountInterestRange.INTEREST_PERCENT_2.getValue());
+ } else if (balance.compareTo(BasicAccountInterestRange.INTEREST_RANGE_3.getValue()) >= 0) {
+ interest = balance.multiply(BasicAccountInterestRange.INTEREST_PERCENT_3.getValue());
+ } else if (balance.compareTo(BasicAccountInterestRange.INTEREST_RANGE_4.getValue()) >= 0) {
+ interest = balance.multiply(BasicAccountInterestRange.INTEREST_PERCENT_4.getValue());
+ } else {
+ interest = balance.multiply(BasicAccountInterestRange.INTEREST_PERCENT_5.getValue());
+ }
+ return interest;
+ }
+}
diff --git a/src/main/java/interest/BasicAccountInterestRange.java b/src/main/java/interest/BasicAccountInterestRange.java
new file mode 100644
index 0000000..ea4bc16
--- /dev/null
+++ b/src/main/java/interest/BasicAccountInterestRange.java
@@ -0,0 +1,22 @@
+package interest;
+
+import java.math.BigDecimal;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+public enum BasicAccountInterestRange {
+ INTEREST_RANGE_1(BigDecimal.valueOf(100000000)),
+ INTEREST_PERCENT_1(BigDecimal.valueOf(0.5)),
+ INTEREST_RANGE_2(BigDecimal.valueOf(5000000)),
+ INTEREST_PERCENT_2(BigDecimal.valueOf(0.07)),
+ INTEREST_RANGE_3 (BigDecimal.valueOf(1000000)),
+ INTEREST_PERCENT_3(BigDecimal.valueOf(0.04)),
+ INTEREST_RANGE_4(BigDecimal.valueOf(10000)),
+ INTEREST_PERCENT_4(BigDecimal.valueOf(0.02)),
+ INTEREST_PERCENT_5(BigDecimal.valueOf(0.01));
+
+ final private BigDecimal value;
+}
diff --git a/src/main/java/interest/InterestCalculator.java b/src/main/java/interest/InterestCalculator.java
new file mode 100644
index 0000000..02298d2
--- /dev/null
+++ b/src/main/java/interest/InterestCalculator.java
@@ -0,0 +1,6 @@
+package interest;
+import java.math.BigDecimal;
+
+public interface InterestCalculator {
+ public BigDecimal getInterest(BigDecimal balance);
+}
diff --git a/src/main/java/interest/SavingAccountInterest.java b/src/main/java/interest/SavingAccountInterest.java
new file mode 100644
index 0000000..b07f475
--- /dev/null
+++ b/src/main/java/interest/SavingAccountInterest.java
@@ -0,0 +1,15 @@
+package interest;
+import java.math.BigDecimal;
+
+public class SavingAccountInterest implements InterestCalculator {
+ @Override
+ public BigDecimal getInterest(BigDecimal balance) {
+ BigDecimal interest;
+ if (balance.compareTo(SavingAccountInterestRange.INTEREST_RANGE_1.getValue()) >= 0) {
+ interest = balance.multiply(SavingAccountInterestRange.INTEREST_PERCENT_1.getValue());
+ } else {
+ interest = balance.multiply(SavingAccountInterestRange.INTEREST_PERCENT_2.getValue());
+ }
+ return interest;
+ }
+}
diff --git a/src/main/java/interest/SavingAccountInterestRange.java b/src/main/java/interest/SavingAccountInterestRange.java
new file mode 100644
index 0000000..16704e9
--- /dev/null
+++ b/src/main/java/interest/SavingAccountInterestRange.java
@@ -0,0 +1,16 @@
+package interest;
+
+import java.math.BigDecimal;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+public enum SavingAccountInterestRange {
+ INTEREST_RANGE_1(BigDecimal.valueOf(1000000)),
+ INTEREST_PERCENT_1(BigDecimal.valueOf(0.5)),
+ INTEREST_PERCENT_2(BigDecimal.valueOf(0.01));
+
+ private final BigDecimal value;
+}
diff --git a/src/main/java/model/AccountRepository.java b/src/main/java/model/AccountRepository.java
new file mode 100644
index 0000000..5d20da7
--- /dev/null
+++ b/src/main/java/model/AccountRepository.java
@@ -0,0 +1,12 @@
+package model;
+
+import account.Account;
+
+public interface AccountRepository {
+
+ void addAccount(Account account);
+
+ void deleteAccount(Account account);
+
+ Account getAccount(String accountNumber);
+}
diff --git a/src/main/java/model/AccountRepositoryImpl.java b/src/main/java/model/AccountRepositoryImpl.java
new file mode 100644
index 0000000..a9c5f2f
--- /dev/null
+++ b/src/main/java/model/AccountRepositoryImpl.java
@@ -0,0 +1,30 @@
+package model;
+
+import account.Account;
+import java.util.HashMap;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class AccountRepositoryImpl implements AccountRepository {
+ private final HashMap accounts = new HashMap<>();
+
+ @Getter
+ private static final AccountRepository instance = new AccountRepositoryImpl();
+
+ @Override
+ public void addAccount(Account account) {
+ accounts.put(account.getAccountNumber(), account);
+ }
+
+ @Override
+ public void deleteAccount(Account account) {
+ accounts.remove(account.getAccountNumber());
+ }
+
+ @Override
+ public Account getAccount(String accountNumber) {
+ return accounts.get(accountNumber);
+ }
+}
diff --git a/src/main/java/model/InterestRepository.java b/src/main/java/model/InterestRepository.java
new file mode 100644
index 0000000..cae7cd1
--- /dev/null
+++ b/src/main/java/model/InterestRepository.java
@@ -0,0 +1,8 @@
+package model;
+
+import account.AccountType;
+import interest.InterestCalculator;
+
+public interface InterestRepository {
+ InterestCalculator getCalculator(AccountType accountType);
+}
diff --git a/src/main/java/model/InterestRepositoryImpl.java b/src/main/java/model/InterestRepositoryImpl.java
new file mode 100644
index 0000000..8f015e9
--- /dev/null
+++ b/src/main/java/model/InterestRepositoryImpl.java
@@ -0,0 +1,24 @@
+package model;
+
+import account.AccountType;
+import interest.InterestCalculator;
+import java.util.HashMap;
+import lombok.Getter;
+
+public class InterestRepositoryImpl implements InterestRepository {
+ private final HashMap interestPolicies = new HashMap<>();
+
+ @Getter
+ private static final InterestRepository instance = new InterestRepositoryImpl();
+
+ private InterestRepositoryImpl() {
+ for (AccountType accountType : AccountType.values()){
+ interestPolicies.put(accountType, accountType.getInterestCalculator());
+ }
+ }
+
+ @Override
+ public InterestCalculator getCalculator(AccountType accountType) {
+ return interestPolicies.get(accountType);
+ }
+}
diff --git a/src/main/java/service/AccountService.java b/src/main/java/service/AccountService.java
new file mode 100644
index 0000000..6d2740a
--- /dev/null
+++ b/src/main/java/service/AccountService.java
@@ -0,0 +1,22 @@
+package service;
+
+import account.Account;
+import java.math.BigDecimal;
+
+public interface AccountService {
+ void addAccount(Account account);
+
+ void deleteAccount(Account account);
+
+ void activate(Account account);
+
+ void deactivate(Account account);
+
+ void deposit(Account account, BigDecimal depositAmount);
+
+ void withdrawal(Account account, BigDecimal withdrawalAmount);
+
+ Account getAccount(String accountNumber);
+
+ boolean checkNumber(String accountNumber);
+}
\ No newline at end of file
diff --git a/src/main/java/service/AccountServiceImpl.java b/src/main/java/service/AccountServiceImpl.java
new file mode 100644
index 0000000..ace3844
--- /dev/null
+++ b/src/main/java/service/AccountServiceImpl.java
@@ -0,0 +1,60 @@
+package service;
+
+import account.Account;
+import java.math.BigDecimal;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import model.AccountRepository;
+import model.AccountRepositoryImpl;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class AccountServiceImpl implements AccountService {
+ private final AccountRepository accountRepository = AccountRepositoryImpl.getInstance();
+
+ @Getter
+ private static final AccountService instance = new AccountServiceImpl();
+
+ @Override
+ public void addAccount(Account account) {
+ if(accountRepository.getAccount(account.getAccountNumber()) == null) {
+ accountRepository.addAccount(account);
+ }
+ }
+
+ @Override
+ public void deleteAccount(Account account) {
+ accountRepository.deleteAccount(account);
+ }
+
+ @Override
+ public Account getAccount(String accountNumber) {
+ return accountRepository.getAccount(accountNumber);
+ }
+
+ @Override
+ public boolean checkNumber(String accountNumber) {
+ Account account = accountRepository.getAccount(accountNumber);
+ return account != null;
+ }
+
+ @Override
+ public void deposit(Account account, BigDecimal depositAmount) {
+ account.deposit(depositAmount);
+ }
+
+ @Override
+ public void withdrawal(Account account, BigDecimal withdrawalAmount) {
+ account.withdrawal(withdrawalAmount);
+ }
+
+ @Override
+ public void deactivate(Account account) {
+ account.deactivate();
+ }
+
+ @Override
+ public void activate(Account account) {
+ account.activate();
+ }
+}
diff --git a/src/main/java/service/InterestService.java b/src/main/java/service/InterestService.java
new file mode 100644
index 0000000..ab4ec96
--- /dev/null
+++ b/src/main/java/service/InterestService.java
@@ -0,0 +1,8 @@
+package service;
+
+import account.AccountType;
+import java.math.BigDecimal;
+
+public interface InterestService {
+ BigDecimal getInterest(AccountType accountType, BigDecimal balance);
+}
diff --git a/src/main/java/service/InterestServiceImpl.java b/src/main/java/service/InterestServiceImpl.java
new file mode 100644
index 0000000..1ebdb18
--- /dev/null
+++ b/src/main/java/service/InterestServiceImpl.java
@@ -0,0 +1,24 @@
+package service;
+
+import account.AccountType;
+import interest.InterestCalculator;
+import java.math.BigDecimal;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import model.InterestRepository;
+import model.InterestRepositoryImpl;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class InterestServiceImpl implements InterestService {
+ private final InterestRepository interestRepository = InterestRepositoryImpl.getInstance();
+
+ @Getter
+ private static final InterestService instance = new InterestServiceImpl();
+
+ @Override
+ public BigDecimal getInterest(AccountType accountType, BigDecimal balance) {
+ InterestCalculator interestCalculator = interestRepository.getCalculator(accountType);
+ return interestCalculator.getInterest(balance);
+ }
+}
diff --git a/src/main/java/util/AccountUtil.java b/src/main/java/util/AccountUtil.java
new file mode 100644
index 0000000..fe14939
--- /dev/null
+++ b/src/main/java/util/AccountUtil.java
@@ -0,0 +1,19 @@
+package util;
+
+import account.AccountType;
+import common.Message;
+import exception.IndexOutOfRangeException;
+
+public class AccountUtil {
+ public static AccountType intToAccountType(int index) throws IndexOutOfRangeException {
+ if (index < AccountType.MIN_INDEX || index > AccountType.MAX_INDEX) {
+ throw new IndexOutOfRangeException(Message.IndexOutOfRange, AccountType.MIN_INDEX, AccountType.MAX_INDEX, index);
+ }
+ for (AccountType accountType : AccountType.values()) {
+ if (index == (accountType.ordinal() + AccountType.MIN_INDEX)) {
+ return accountType;
+ }
+ }
+ throw new IndexOutOfRangeException(Message.IndexOutOfRange, AccountType.MIN_INDEX, AccountType.MAX_INDEX, index);
+ }
+}
diff --git a/src/main/java/util/MenuUtil.java b/src/main/java/util/MenuUtil.java
new file mode 100644
index 0000000..9f5a2b9
--- /dev/null
+++ b/src/main/java/util/MenuUtil.java
@@ -0,0 +1,19 @@
+package util;
+
+import common.Message;
+import exception.IndexOutOfRangeException;
+import view.EMenu;
+
+public class MenuUtil {
+ public static EMenu intToMenu(int index) throws IndexOutOfRangeException {
+ if (index < EMenu.MIN_INDEX || index > EMenu.MAX_INDEX) {
+ throw new IndexOutOfRangeException(Message.IndexOutOfRange, EMenu.MIN_INDEX, EMenu.MAX_INDEX, index);
+ }
+ for (EMenu eMenu : EMenu.values()) {
+ if (index == eMenu.ordinal() + EMenu.MIN_INDEX) {
+ return eMenu;
+ }
+ }
+ throw new IndexOutOfRangeException(Message.IndexOutOfRange, EMenu.MIN_INDEX, EMenu.MAX_INDEX, index);
+ }
+}
diff --git a/src/main/java/view/BankingView.java b/src/main/java/view/BankingView.java
new file mode 100644
index 0000000..55f387b
--- /dev/null
+++ b/src/main/java/view/BankingView.java
@@ -0,0 +1,218 @@
+package view;
+
+import account.Account;
+import account.AccountType;
+import application.BankingManager;
+import common.EParamType;
+import common.ErrorMessage;
+import common.Message;
+import common.RequireMessage;
+import common.Response;
+import exception.DeactivatedAccountException;
+import exception.IndexOutOfRangeException;
+import exception.NotExistAccountException;
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.util.HashMap;
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
+import util.AccountUtil;
+import util.MenuUtil;
+
+@RequiredArgsConstructor(access = AccessLevel.PUBLIC)
+public class BankingView {
+ private final BankingManager bankingManager;
+ private final InputView inputView;
+ private final OutputView outputView;
+ public boolean run(){
+ EMenu eMenu = selectMenu();
+ if(eMenu == null){
+ return true;
+ }
+ return switch(eMenu) {
+ case eMakeAccount -> makeAccount();
+ case eWithdrawal -> withdrawal();
+ case eDeposit -> deposit();
+ case eRemittance -> remittance();
+ case eAccountInfo -> getAccountInfo();
+ case eInterest -> getInterest();
+ case eDeactivateAccount -> deactivateAccount();
+ case eActivateAccount -> activateAccount();
+ case eDeleteAccount -> deleteAccount();
+ case eQuit -> {
+ outputView.print(Message.Exit);
+ yield false;
+ }
+ };
+ }
+
+ private EMenu selectMenu(){
+ outputView.print(Message.SELECT_MENU);
+ try {
+ return MenuUtil.intToMenu(inputView.getInt());
+ } catch (NumberFormatException | IndexOutOfRangeException exception) {
+ outputView.printError(exception.getMessage());
+ }
+ return null;
+ }
+
+ private boolean makeAccount() {
+ try {
+ outputView.print(RequireMessage.RequireAccountType);
+ AccountType accountType =
+ AccountUtil.intToAccountType(inputView.getInt());
+ HashMap params =
+ accountType.getAccountMaker().makeAccount(inputView, outputView);
+ Response response = bankingManager.makeAccount(params);
+ Account account = (Account) response.getMessage();
+ if(account.isEmpty()){
+ outputView.print(Message.AccountCreationFailed);
+ } else {
+ outputView.print(account.getAccountInfo());
+ }
+ } catch (NumberFormatException | IndexOutOfRangeException e) {
+ outputView.printError(e.getMessage());
+ } catch (NotExistAccountException e) {
+ outputView.printError(ErrorMessage.NotExistAccount);
+ } catch (DeactivatedAccountException e) {
+ outputView.printError(ErrorMessage.DeactivatedAccount);
+ }
+ return true;
+ }
+
+ private boolean deleteAccount() {
+ try{
+ outputView.print(Message.Delete.getPrintMessage() + " " +
+ RequireMessage.RequireAccountNumber.getPrintMessage());
+ Response response = bankingManager.deleteAccount(inputView.getString());
+ Message result = (Message) response.getMessage();
+ outputView.print(result.getPrintMessage());
+ } catch (NotExistAccountException exception){
+ outputView.printError(ErrorMessage.NotExistAccount);
+ } catch (DeactivatedAccountException e) {
+ outputView.printError(ErrorMessage.DeactivatedAccount);
+ }
+ return true;
+ }
+
+ private boolean activateAccount() {
+ try{
+ outputView.print(Message.Activate.getPrintMessage() + " "
+ + RequireMessage.RequireAccountNumber.getPrintMessage());
+ Response response = bankingManager.activateAccount(inputView.getString());
+ Message result = (Message) response.getMessage();
+ outputView.print(result.getPrintMessage());
+ } catch (NotExistAccountException exception){
+ outputView.printError(ErrorMessage.NotExistAccount);
+ } catch (DeactivatedAccountException e) {
+ outputView.printError(ErrorMessage.DeactivatedAccount);
+ }
+ return true;
+ }
+
+ private boolean deactivateAccount() {
+ try{
+ outputView.print(Message.Deactivate.getPrintMessage() + " "
+ + RequireMessage.RequireAccountNumber.getPrintMessage());
+ Response response = bankingManager.deactivateAccount(inputView.getString());
+ Message result = (Message) response.getMessage();
+ outputView.print(result.getPrintMessage());
+ } catch (NotExistAccountException exception){
+ outputView.printError(ErrorMessage.NotExistAccount);
+ } catch (DeactivatedAccountException e) {
+ outputView.printError(ErrorMessage.DeactivatedAccount);
+ }
+ return true;
+ }
+
+ private boolean getAccountInfo() {
+ try {
+ outputView.print(RequireMessage.RequireAccountNumber.getPrintMessage());
+ Response response = bankingManager.getAccount(inputView.getString());
+ Account result = (Account) response.getMessage();
+ outputView.print(result.getAccountInfo());
+ } catch (NotExistAccountException exception){
+ outputView.printError(ErrorMessage.NotExistAccount);
+ } catch (DeactivatedAccountException e) {
+ outputView.printError(ErrorMessage.DeactivatedAccount);
+ }
+ return true;
+ }
+
+ private boolean getInterest() {
+ try {
+ outputView.print(Message.Interest.getPrintMessage() + " "
+ + RequireMessage.RequireAccountNumber.getPrintMessage());
+ Response response = bankingManager.getInterest(inputView.getString());
+ BigDecimal interest = (BigDecimal) response.getMessage();
+ outputView.print(Message.Interest.getPrintMessage() + " ₩"
+ + new DecimalFormat("###,###").format(interest));
+ } catch (NotExistAccountException exception){
+ outputView.printError(ErrorMessage.NotExistAccount);
+ } catch (DeactivatedAccountException e) {
+ outputView.printError(ErrorMessage.DeactivatedAccount);
+ }
+ return true;
+ }
+
+ private boolean remittance() {
+ try {
+ outputView.print(Message.Withdrawal.getPrintMessage() + " "
+ + RequireMessage.RequireAccountNumber.getPrintMessage());
+ String withDrawalAccountNumber = inputView.getString();
+ outputView.print(Message.Deposit.getPrintMessage() + " "
+ + RequireMessage.RequireAccountNumber.getPrintMessage());
+ String depositAccountNumber = inputView.getString();
+ outputView.print(Message.Remittance.getPrintMessage() + " "
+ + RequireMessage.RequireAmount.getPrintMessage());
+ BigDecimal amount = inputView.getBigDecimal();
+ Response response =
+ bankingManager.remittance(withDrawalAccountNumber, depositAccountNumber, amount);
+ Message result = (Message) response.getMessage();
+ outputView.print(result.getPrintMessage());
+ } catch (NotExistAccountException exception){
+ outputView.printError(ErrorMessage.NotExistAccount);
+ } catch (DeactivatedAccountException e) {
+ outputView.printError(ErrorMessage.DeactivatedAccount);
+ }
+ return true;
+ }
+
+ private boolean deposit() {
+ try {
+ outputView.print(Message.Deposit.getPrintMessage() + " "
+ + RequireMessage.RequireAccountNumber.getPrintMessage());
+ String accountNumber = inputView.getString();
+ outputView.print(Message.Deposit.getPrintMessage() + " "
+ + RequireMessage.RequireAmount.getPrintMessage());
+ BigDecimal depositAmount = inputView.getBigDecimal();
+ Response response = bankingManager.deposit(accountNumber, depositAmount);
+ Message message = (Message) response.getMessage();
+ outputView.print(message.getPrintMessage());
+ } catch (NotExistAccountException exception){
+ outputView.printError(ErrorMessage.NotExistAccount);
+ } catch (DeactivatedAccountException e) {
+ outputView.printError(ErrorMessage.DeactivatedAccount);
+ }
+ return true;
+ }
+
+ private boolean withdrawal() {
+ try {
+ outputView.print(Message.Withdrawal.getPrintMessage() + " "
+ + RequireMessage.RequireAccountNumber.getPrintMessage());
+ String accountNumber = inputView.getString();
+ outputView.print(Message.Withdrawal.getPrintMessage() + " "
+ + RequireMessage.RequireAmount.getPrintMessage());
+ BigDecimal withdrawalAmount = inputView.getBigDecimal();
+ Response response = bankingManager.withdrawal(accountNumber, withdrawalAmount);
+ Message result = (Message) response.getMessage();
+ outputView.print(result.getPrintMessage());
+ } catch (NotExistAccountException exception){
+ outputView.printError(ErrorMessage.NotExistAccount);
+ } catch (DeactivatedAccountException e) {
+ outputView.printError(ErrorMessage.DeactivatedAccount);
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/view/EMenu.java b/src/main/java/view/EMenu.java
new file mode 100644
index 0000000..69eb3d9
--- /dev/null
+++ b/src/main/java/view/EMenu.java
@@ -0,0 +1,24 @@
+package view;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+public enum EMenu {
+ eMakeAccount("계좌 생성"),
+ eWithdrawal("출금"),
+ eDeposit("입금"),
+ eRemittance("송금"),
+ eAccountInfo("계좌 정보 확인"),
+ eInterest("이자 확인"),
+ eDeactivateAccount("계좌 비활성화"),
+ eActivateAccount("계좌 활성화"),
+ eDeleteAccount("계좌 삭제"),
+ eQuit("종료");
+
+ public static final int MIN_INDEX = 1;
+ public static final int MAX_INDEX = EMenu.values().length;
+ private final String menuText;
+}
diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java
new file mode 100644
index 0000000..3d93fb5
--- /dev/null
+++ b/src/main/java/view/InputView.java
@@ -0,0 +1,13 @@
+package view;
+
+import java.math.BigDecimal;
+
+public interface InputView {
+ int getInt() throws NumberFormatException;
+
+ String getString();
+
+ BigDecimal getBigDecimal() throws NumberFormatException;
+
+ boolean getBoolean() throws NumberFormatException;
+}
diff --git a/src/main/java/view/InputViewImpl.java b/src/main/java/view/InputViewImpl.java
new file mode 100644
index 0000000..44dec75
--- /dev/null
+++ b/src/main/java/view/InputViewImpl.java
@@ -0,0 +1,42 @@
+package view;
+
+import java.math.BigDecimal;
+import java.util.Scanner;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class InputViewImpl implements InputView {
+ private final Scanner scanner = new Scanner(System.in);
+
+ @Getter
+ private static final InputView instance = new InputViewImpl();
+
+ @Override
+ public int getInt() throws NumberFormatException{
+ return scanner.nextInt();
+ }
+
+ @Override
+ public String getString(){
+ return scanner.next();
+ }
+
+ @Override
+ public BigDecimal getBigDecimal() throws NumberFormatException {
+ return new BigDecimal(scanner.next());
+ }
+
+ @Override
+ public boolean getBoolean() throws NumberFormatException{
+ int input = scanner.nextInt();
+ if (input == 0) {
+ return false;
+ } else if (input == 1) {
+ return true;
+ } else {
+ throw new NumberFormatException("0 또는 1만 입력해주세요.");
+ }
+ }
+}
diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java
new file mode 100644
index 0000000..08ac94a
--- /dev/null
+++ b/src/main/java/view/OutputView.java
@@ -0,0 +1,17 @@
+package view;
+
+import common.ErrorMessage;
+import common.Message;
+import common.RequireMessage;
+
+public interface OutputView {
+ void print(String printMessage);
+
+ void print(RequireMessage requireMessage);
+
+ void print(Message message);
+
+ void printError(ErrorMessage errorMessage);
+
+ void printError(String errorMessage);
+}
\ No newline at end of file
diff --git a/src/main/java/view/OutputViewImpl.java b/src/main/java/view/OutputViewImpl.java
new file mode 100644
index 0000000..d1ed0c9
--- /dev/null
+++ b/src/main/java/view/OutputViewImpl.java
@@ -0,0 +1,37 @@
+package view;
+
+import common.ErrorMessage;
+import common.Message;
+import common.RequireMessage;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class OutputViewImpl implements OutputView {
+ @Getter
+ private static final OutputView instance = new OutputViewImpl();
+
+ @Override
+ public void print(String printMessage) {
+ System.out.println(printMessage);
+ }
+
+ public void print(RequireMessage requireMessage) {
+ System.out.println(requireMessage.getPrintMessage());
+ }
+
+ public void print(Message message) {
+ System.out.println(message.getPrintMessage());
+ }
+
+ @Override
+ public void printError(ErrorMessage errorMessage) {
+ printError(errorMessage.getPrintMessage());
+ }
+
+ @Override
+ public void printError(String errorMessage) {
+ System.out.println("[Error] : " + errorMessage);
+ }
+}