Menu
Coddy logo textTech

은행 시스템

Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 107개 중 106번째.

challenge icon

챌린지

쉬움

account, transaction, financial operation을 관리하는 Banking System을 만들어 봅시다! 데이터 무결성이 중요한 견고한 시스템을 구축하게 됩니다. 즉, account 잔액은 검증된 메서드를 통해서만 변경되어야 하며, 초과 인출이나 유효하지 않은 금액과 같은 오류는 우아하게 처리해야 합니다.

코드를 다섯 개의 파일로 구성합니다.

  • account.go: Deposit(amount float64) errorWithdraw(amount float64) error 메서드를 포함하는 Transactable 인터페이스를 Define합니다. 그런 다음 이 인터페이스를 구현하는 두 가지 account 유형을 만듭니다.

    내보내지 않는 id, holder, balance 필드를 가진 CheckingAccount입니다. Checking account는 balance를 정확히 0으로 만드는 withdrawal은 허용하지만, 0 미만으로 만드는 withdrawal은 허용하지 않습니다.

    동일한 내보내지 않는 필드와 함께 minBalance 필드를 추가로 가진 SavingsAccount입니다. Savings account는 100.0의 최소 balance를 유지해야 하므로, 이 기준 아래로 balance를 낮추는 withdrawal은 실패해야 합니다.

    두 account 유형 모두 constructor(NewCheckingAccountNewSavingsAccount)와 Balance() float64 getter 메서드가 필요합니다. deposit에서는 음수 또는 0인 금액을 거부합니다. withdrawal에서는 음수 또는 0인 금액과 available funds를 초과하는 금액을 거부합니다.

  • transaction.go: account activity를 기록하기 위한 Transaction struct를 Create합니다. 각 transaction에는 Type(string: "deposit" 또는 "withdrawal"), Amount(float64), AccountID(string)가 있습니다. NewTransaction constructor와, 다음 형식을 반환하는 String() string 메서드를 포함합니다: 금액을 소수점 둘째 자리까지 표시한 [Type] $[Amount] on [AccountID].
  • bank.go: 여러 account를 관리하고 transaction을 기록하는 Bank struct를 구축합니다. ID를 기준으로 map에 account를 저장하고 transaction의 slice를 유지합니다. 다음을 구현합니다.
    • NewBank() *Bank: 빈 collections를 초기화합니다.
    • AddAccount(id string, account Transactable): account를 등록합니다.
    • Deposit(accountID string, amount float64) error: account에 deposit하고 성공 시 transaction을 기록합니다.
    • Withdraw(accountID string, amount float64) error: account에서 withdrawal하고 성공 시 transaction을 기록합니다.
    • Transfer(fromID, toID string, amount float64) error: account 사이에서 atomically 자금을 이동합니다(withdrawal은 성공했지만 deposit이 실패하면 withdrawal을 되돌립니다).
    • GetBalance(accountID string) (float64, error): account의 balance를 반환합니다.

    존재하지 않는 account에 대해서는 appropriate 오류를 반환합니다.

  • errors.go: banking operation을 위한 sentinel 오류를 Define합니다.
    • ErrAccountNotFound: account ID가 존재하지 않을 때
    • ErrInsufficientFunds: withdrawal이 available balance를 초과할 때
    • ErrInvalidAmount: deposit/withdrawal 금액이 0 또는 음수일 때
    • ErrMinBalanceRequired: Savings withdrawal이 최소 balance를 위반할 때
  • main.go: bank를 Create하고 입력에 따라 operation을 처리합니다.

    Create할 account의 수를 읽습니다. 각 account에 대해 account 유형(checking 또는 savings), ID, holder 이름, initial balance를 읽습니다. appropriate account 유형을 Create하여 bank에 Add합니다.

    그런 다음 operation의 수를 읽습니다. 각 operation은 다음 중 하나입니다.

    • deposit [accountID] [amount]
    • withdraw [accountID] [amount]
    • transfer [fromID] [toID] [amount]
    • balance [accountID]

    각 operation의 결과를 출력합니다. 성공한 deposit/withdrawal/transfer에는 OK를, balance query에는 balance를 소수점 둘째 자리까지 표시하여 출력하고, 실패한 경우에는 sentinel 오류 메시지를 사용하여 Error: [message]를 출력합니다.

다음 입력이 제공됩니다.

  • account의 수, 이어서 각 account의 type, ID, holder, initial balance(각 account당 4줄)
  • operation의 수, 이어서 각 operation(각 1줄)

예를 들어 다음이 주어졌다고 합시다.

2
checking
ACC001
Alice
500.00
savings
ACC002
Bob
1000.00
6
deposit ACC001 200
balance ACC001
withdraw ACC002 950
withdraw ACC001 800
transfer ACC001 ACC002 100
balance ACC002

출력은 다음과 같아야 합니다.

OK
700.00
Error: minimum balance required
Error: insufficient funds
OK
1000.00

그리고 다음이 주어졌다고 합시다.

1
checking
C100
Charlie
100.00
4
withdraw C100 100
balance C100
deposit C100 -50
withdraw C999 50

출력은 다음과 같아야 합니다.

OK
0.00
Error: invalid amount
Error: account not found

Transactable 인터페이스를 사용하면 Bank가 두 account 유형을 모두 동일한 방식으로 다룰 수 있으며, 각 account 유형은 자체 규칙을 적용한다는 점에 주목하세요. Transfer 메서드는 atomic operation을 보여 줍니다. 처리 도중 문제가 발생하더라도 system은 일관된 상태를 유지합니다!

직접 해보기

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)

	// 계좌 수 읽기
	scanner.Scan()
	numAccounts, _ := strconv.Atoi(scanner.Text())

	// 새 은행 생성
	bank := NewBank()

	// 계좌 읽고 생성
	for i := 0; i < numAccounts; i++ {
		scanner.Scan()
		accountType := scanner.Text()
		scanner.Scan()
		id := scanner.Text()
		scanner.Scan()
		holder := scanner.Text()
		scanner.Scan()
		balance, _ := strconv.ParseFloat(scanner.Text(), 64)

		// TODO: accountType에 따라 적절한 계좌 유형 생성
		// 그리고 은행에 추가
		_ = accountType
		_ = id
		_ = holder
		_ = balance
	}

	// 작업 수 읽기
	scanner.Scan()
	numOps, _ := strconv.Atoi(scanner.Text())

	// 각 작업 처리
	for i := 0; i < numOps; i++ {
		scanner.Scan()
		parts := strings.Fields(scanner.Text())
		operation := parts[0]

		// TODO: 각 작업 유형 처리:
		// - "deposit": 계좌에 입금, "OK" 또는 오류 출력
		// - "withdraw": 계좌에서 출금, "OK" 또는 오류 출력
		// - "transfer": 계좌 간 이체, "OK" 또는 오류 출력
		// - "balance": 잔액을 가져와 소수점 2자리로 포맷하여 출력
		_ = operation
	}
}

객체 지향 프로그래밍의 모든 레슨

직접 연습해 보세요: 온라인 Go 컴파일러