银行系统
本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 106 课,共 107 课。
挑战
简单让我们构建一个管理账户、交易和金融操作的银行系统!你将创建一个健壮的系统,其中数据完整性至关重要:账户余额只能通过经过验证的方法更改,透支或金额无效等错误必须得到妥善处理。
你将把代码分布在 5 个文件中:
account.go:定义一个Transactable接口,其中包含Deposit(amount float64) error和Withdraw(amount float64) error方法。然后创建两种实现此接口的账户类型:一种
CheckingAccount,包含未导出的id、holder和balance字段。支票账户允许取款后余额恰好为零,但不能低于零。一种
SavingsAccount,包含相同的未导出字段,另外还有一个minBalance字段。储蓄账户必须保持至少 100.0 的余额:如果取款会使余额低于此阈值,就必须失败。这两种账户类型都需要构造函数(
NewCheckingAccount和NewSavingsAccount)以及Balance() float64getter 方法。存款时,拒绝负数或零金额。取款时,拒绝负数或零金额,以及超过可用资金的金额。transaction.go:创建一个Transaction结构体,用于记录账户活动。每笔交易都有一个Type(字符串:"deposit" 或 "withdrawal")、Amount(float64)和AccountID(字符串)。添加一个NewTransaction构造函数,以及一个String() string方法,该方法返回以下格式:[Type] $[Amount] on [AccountID],其中金额格式为两位小数。bank.go:构建一个Bank结构体,用于管理多个账户并记录交易。按 ID 将账户存储在 map 中,并使用切片保存交易。实现以下内容: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;查询余额时打印格式为两位小数的余额;操作失败时,使用哨兵错误消息打印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 found注意,Transactable 接口让 Bank 能够统一处理这两种账户类型,同时每种账户类型都能执行自己的规则。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
}
}
面向对象编程中的所有课
自己练习: Go 在线编译器