銀行システム
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 106/107。
チャレンジ
簡単口座、取引、および財務操作を管理する銀行システムを構築しましょう!データの整合性が極めて重要な、堅牢なシステムを作成します。口座残高は検証されたメソッドを通じてのみ変更されるべきであり、オーバードラフト(残高不足)や無効な金額などのエラーは適切に処理される必要があります。
コードは以下の5つのファイルに分けて構成します:
account.go:Deposit(amount float64) errorとWithdraw(amount float64) errorメソッドを持つTransactableインターフェースを定義します。次に、このインターフェースを実装する2つの口座タイプを作成します:非公開の
id、holder、およびbalanceフィールドを持つCheckingAccount。当座預金口座(Checking accounts)は、残高がちょうどゼロになるまでの引き出しを許可しますが、ゼロを下回ることはできません。同じ非公開フィールドに加えて
minBalanceフィールドを持つSavingsAccount。普通預金口座(Savings accounts)は、100.0 の最小残高を維持する必要があります。このしきい値を下回るような引き出しは失敗させる必要があります。両方の口座タイプには、コンストラクタ(
NewCheckingAccountとNewSavingsAccount)およびBalance() float64ゲッターメソッドが必要です。入金については、負の金額またはゼロを拒否してください。引き出しについては、負の金額、ゼロ、および利用可能な資金を超える金額を拒否してください。transaction.go: 口座のアクティビティを記録するためのTransaction構造体を作成します。各取引にはType(文字列: "deposit" または "withdrawal")、Amount(float64)、およびAccountID(文字列)があります。NewTransactionコンストラクタと、[Type] $[Amount] on [AccountID]の形式(金額は小数点以下2桁にフォーマット)を返すString() stringメソッドを含めてください。bank.go: 複数の口座を管理し、取引を記録するBank構造体を構築します。口座はIDをキーとするマップに保存し、取引のスライスを保持します。以下を実装してください:NewBank() *Bank— 空のコレクションを初期化しますAddAccount(id string, account Transactable)— 口座を登録しますDeposit(accountID string, amount float64) error— 口座に入金し、成功時に取引を記録しますWithdraw(accountID string, amount float64) error— 口座から引き出し、成功時に取引を記録しますTransfer(fromID, toID string, amount float64) error— 口座間で資金をアトミックに移動します(引き出しに成功しても入金に失敗した場合は、引き出しを取り消します)GetBalance(accountID string) (float64, error)— 口座の残高を返します
存在しない口座に対しては、適切なエラーを返してください。
errors.go: 銀行操作のためのセンチネルエラーを定義します:ErrAccountNotFound— 口座IDが存在しない場合ErrInsufficientFunds— 引き出し額が利用可能な残高を超える場合ErrInvalidAmount— 入金/引き出し額がゼロまたは負の場合ErrMinBalanceRequired— 普通預金の引き出しが最小残高に違反する場合
main.go: 銀行を作成し、入力に基づいて操作を処理します。作成する口座の数を読み取ります。各口座について、口座タイプ(
checkingまたはsavings)、ID、名義人名、および初期残高を読み取ります。適切な口座タイプを作成し、銀行に追加します。次に、操作の数を読み取ります。各操作は以下のいずれかです:
deposit [accountID] [amount]withdraw [accountID] [amount]transfer [fromID] [toID] [amount]balance [accountID]
各操作について、結果を出力します。入金/引き出し/振込が成功した場合は
OK、残高照会の場合は小数点以下2桁にフォーマットされた残高、失敗した場合はセンチネルエラーメッセージを使用してError: [message]を出力します。
以下の入力が提供されます:
- 口座数、続いて各口座のタイプ、ID、名義人、初期残高(各4行)
- 操作数、続いて各操作(各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 foundTransactable インターフェースによって、銀行が両方の口座タイプを一律に扱える一方で、各口座タイプが独自のルールを適用していることに注目してください。Transferメソッドはアトミックな操作を示しており、途中で問題が発生しても、システムの整合性が保たれます!
自分で試してみよう
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
}
}
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー