銀行システム
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 106/107。
チャレンジ
簡単口座、取引、金融操作を管理する Banking System を構築しましょう!データの整合性が重要となる堅牢なシステムを作成します。口座残高は検証済みのメソッドを通じてのみ変更され、残高不足や無効な金額などのエラーは適切に処理される必要があります。
コードを 5 つのファイルに分けて整理します。
account.go:Deposit(amount float64) errorメソッドとWithdraw(amount float64) errorメソッドを持つTransactableインターフェースを Define します。次に、このインターフェースを実装する 2 種類の口座を作成します。非エクスポートの
id、holder、balanceフィールドを持つCheckingAccount。CheckingAccount では、残高を正確にゼロにする引き出しは Allow しますが、ゼロを下回ることは許可しません。同じ非エクスポートフィールドに加えて
minBalanceフィールドを持つSavingsAccount。SavingsAccount は 100.0 の最低残高を維持する必要があり、このしきい値を下回る引き出しは失敗させます。両方の口座タイプにはコンストラクター(
NewCheckingAccountとNewSavingsAccount)およびBalance() float64getter メソッドが必要です。入金では、負の金額またはゼロの金額を拒否します。引き出しでは、負の金額、ゼロの金額、および available な資金を超える金額を拒否します。transaction.go: 口座の activity を記録するTransaction構造体を Create します。各取引には、Type(string: "deposit" または "withdrawal")、Amount(float64)、AccountID(string)が含まれます。NewTransactionコンストラクターと、金額を小数点以下 2 桁でフォーマットして[Type] $[Amount] on [AccountID]形式を返すString() stringメソッドを追加します。bank.go: 複数の口座を管理し、取引を記録するBank構造体を構築します。口座は ID をキーとする map に格納し、取引の slice も保持します。次を実装します。NewBank() *Bank: empty な collections を初期化しますAddAccount(id string, account Transactable): 口座を登録しますDeposit(accountID string, amount float64) error: 口座に入金し、成功時に取引を記録しますWithdraw(accountID string, amount float64) error: 口座から引き出し、成功時に取引を記録しますTransfer(fromID, toID string, amount float64) error: 口座間で atomically に資金を移動します(引き出しが成功して入金が失敗した場合は、引き出しを元に戻します)GetBalance(accountID string) (float64, error): 口座の残高を返します
存在しない口座に対しては appropriate なエラーを返します。
errors.go: banking 操作の sentinel エラーを Define します。ErrAccountNotFound: 口座 ID が存在しない場合ErrInsufficientFunds: 引き出し金額が available な残高を超える場合ErrInvalidAmount: 入金額または引き出し額がゼロまたは負の場合ErrMinBalanceRequired: SavingsAccount の引き出しが最低残高に違反する場合
main.go: Bank を Create し、入力に基づいて操作を処理します。Create する口座数を読み取ります。各口座について、口座タイプ(
checkingまたはsavings)、ID、holder 名、初期残高を読み取ります。appropriate な口座タイプを作成し、Bank に追加します。次に操作数を読み取ります。各操作は次のいずれかです。
deposit [accountID] [amount]withdraw [accountID] [amount]transfer [fromID] [toID] [amount]balance [accountID]
各操作について結果を出力します。入金、引き出し、Transfer が成功した場合は
OK、balance クエリの場合は小数点以下 2 桁でフォーマットした残高、失敗した場合は sentinel エラーメッセージを使用したError: [message]を出力します。
次の入力が提供されます。
- 口座数、その後に各口座の type、ID、holder、初期残高(各 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 インターフェースによって Bank は両方の口座タイプを統一的に扱うことができ、一方で各口座タイプは独自のルールを適用している点に注目してください。Transfer メソッドは atomic な操作を示しています。途中で問題が発生しても、システムは一貫性を保ちます。
自分で試してみよう
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復習 - ファイルパーサー自分で練習してみよう: Goオンラインコンパイラ