Hệ thống ngân hàng
Thuộc phần Lập trình hướng đối tượng trong hành trình GO của Coddy. Bài học 106 trên 107.
Thử thách
DễHãy xây dựng một hệ thống ngân hàng để quản lý tài khoản, giao dịch và các hoạt động tài chính! Bạn sẽ tạo một hệ thống vững chắc, trong đó tính toàn vẹn dữ liệu rất quan trọng: số dư tài khoản chỉ nên thay đổi thông qua các phương thức đã được xác thực, còn những lỗi như rút vượt số dư hoặc số tiền không hợp lệ cần được xử lý phù hợp.
Bạn sẽ sắp xếp code trong năm tệp:
account.go: Định nghĩa một giao diệnTransactablevới các phương thứcDeposit(amount float64) errorvàWithdraw(amount float64) error. Sau đó tạo hai kiểu tài khoản triển khai giao diện này:Một
CheckingAccountcó các trường chưa được xuấtid,holdervàbalance. Tài khoản thanh toán cho phép rút tiền khiến số dư còn đúng bằng 0, nhưng không được thấp hơn 0.Một
SavingsAccountcó các trường chưa được xuất giống như trên, cùng với trườngminBalance. Tài khoản tiết kiệm phải duy trì số dư tối thiểu là 100.0: các giao dịch rút tiền khiến số dư thấp hơn ngưỡng này sẽ bị từ chối.Cả hai kiểu tài khoản đều cần hàm khởi tạo (
NewCheckingAccountvàNewSavingsAccount) cùng phương thức getterBalance() float64. Với giao dịch gửi tiền, hãy từ chối số tiền âm hoặc bằng 0. Với giao dịch rút tiền, hãy từ chối số tiền âm hoặc bằng 0 và số tiền vượt quá khoản tiền hiện có.transaction.go: Tạo một cấu trúcTransactionđể ghi lại hoạt động của tài khoản. Mỗi giao dịch cóType(chuỗi: "deposit" hoặc "withdrawal"),Amount(float64) vàAccountID(chuỗi). Thêm hàm khởi tạoNewTransactionvà phương thứcString() stringtrả về định dạng:[Type] $[Amount] on [AccountID], trong đó số tiền được định dạng với hai chữ số thập phân.bank.go: Xây dựng cấu trúcBankđể quản lý nhiều tài khoản và ghi lại các giao dịch. Lưu tài khoản trong một map theo ID và lưu các giao dịch trong một slice. Triển khai:NewBank() *Bank: khởi tạo các tập hợp rỗngAddAccount(id string, account Transactable): đăng ký một tài khoảnDeposit(accountID string, amount float64) error: gửi tiền vào tài khoản và ghi lại giao dịch nếu thành côngWithdraw(accountID string, amount float64) error: rút tiền từ tài khoản và ghi lại giao dịch nếu thành côngTransfer(fromID, toID string, amount float64) error: chuyển tiền giữa các tài khoản theo cách nguyên tử (nếu rút tiền thành công nhưng gửi tiền thất bại, hãy hoàn lại khoản tiền đã rút)GetBalance(accountID string) (float64, error): trả về số dư của một tài khoản
Trả về lỗi phù hợp khi tài khoản không tồn tại.
errors.go: Định nghĩa các lỗi sentinel cho những hoạt động ngân hàng:ErrAccountNotFound: khi ID tài khoản không tồn tạiErrInsufficientFunds: khi số tiền rút vượt quá số dư hiện cóErrInvalidAmount: khi số tiền gửi hoặc rút bằng 0 hay âmErrMinBalanceRequired: khi giao dịch rút tiền từ tài khoản tiết kiệm vi phạm số dư tối thiểu
main.go: Tạo một ngân hàng và xử lý các hoạt động dựa trên đầu vào.Đọc số lượng tài khoản cần tạo. Với mỗi tài khoản, đọc kiểu tài khoản (
checkinghoặcsavings), ID, tên chủ tài khoản và số dư ban đầu. Tạo kiểu tài khoản phù hợp rồi thêm vào ngân hàng.Sau đó đọc số lượng hoạt động. Mỗi hoạt động thuộc một trong các dạng sau:
deposit [accountID] [amount]withdraw [accountID] [amount]transfer [fromID] [toID] [amount]balance [accountID]
Với mỗi hoạt động, hãy in kết quả:
OKnếu gửi, rút hoặc chuyển tiền thành công; số dư được định dạng với hai chữ số thập phân nếu truy vấn số dư; hoặcError: [message]nếu có lỗi, dùng thông báo lỗi sentinel của bạn.
Đầu vào sẽ có dạng sau:
- Số lượng tài khoản, sau đó là kiểu, ID, chủ tài khoản và số dư ban đầu của từng tài khoản (mỗi tài khoản gồm 4 dòng)
- Số lượng hoạt động, sau đó là từng hoạt động (mỗi hoạt động gồm 1 dòng)
Ví dụ, với đầu vào:
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 ACC002Kết quả đầu ra sẽ là:
OK
700.00
Error: minimum balance required
Error: insufficient funds
OK
1000.00Với đầu vào:
1
checking
C100
Charlie
100.00
4
withdraw C100 100
balance C100
deposit C100 -50
withdraw C999 50Kết quả đầu ra sẽ là:
OK
0.00
Error: invalid amount
Error: account not foundLưu ý giao diện Transactable cho phép Bank làm việc thống nhất với cả hai kiểu tài khoản, trong khi mỗi kiểu tài khoản tự áp dụng các quy tắc riêng. Phương thức Transfer minh họa các hoạt động nguyên tử: nếu có sự cố giữa chừng, hệ thống vẫn giữ được tính nhất quán!
Tự làm thử
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Đọc số lượng tài khoản
scanner.Scan()
numAccounts, _ := strconv.Atoi(scanner.Text())
// Tạo ngân hàng mới
bank := NewBank()
// Đọc thông tin và tạo các tài khoản
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: Tạo tài khoản có loại phù hợp dựa trên accountType
// và thêm tài khoản đó vào ngân hàng
_ = accountType
_ = id
_ = holder
_ = balance
}
// Đọc số lượng thao tác
scanner.Scan()
numOps, _ := strconv.Atoi(scanner.Text())
// Xử lý từng thao tác
for i := 0; i < numOps; i++ {
scanner.Scan()
parts := strings.Fields(scanner.Text())
operation := parts[0]
// TODO: Xử lý từng loại thao tác:
// - "deposit": nạp tiền vào tài khoản, in ra "OK" hoặc lỗi
// - "withdraw": rút tiền từ tài khoản, in ra "OK" hoặc lỗi
// - "transfer": chuyển tiền giữa các tài khoản, in ra "OK" hoặc lỗi
// - "balance": lấy và in số dư với 2 chữ số thập phân
_ = operation
}
}
Tất cả bài học trong Lập trình hướng đối tượng
1Nền tảng OOP trong Go
Tệp bên ngoàiWorkspace và module trong GoPackage và importTên được export và không được exportGiới thiệu về OOP trong GoStruct như lớpĐịnh nghĩa phương thức cho structReceiver kiểu con trỏ và kiểu giá trịKhởi tạo structHàm khởi tạoÔn tập: máy tính đơn giản4Interface
Giới thiệu về interfaceTriển khai ngầm địnhInterface như một hợp đồngInterface rỗng (any)Khẳng định kiểuswitch kiểuKết hợp interfaceInterface Stringer và ErrorÔn tập: Máy tính hình học7Tính đóng gói
Trường được xuất và không được xuấtĐóng gói ở cấp packagePhương thức getter và setterẨn thông tin trong GoÔn tập: hồ sơ học sinh10Generics (Go 1.18 trở lên)
Giới thiệu về GenericsTham số kiểuRàng buộc kiểuStruct tổng quátGiải pháp thay thế cho phương thức tổng quátÔn tập: tập hợp tổng quát13Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếMẫu SingletonMẫu FactoryMẫu Abstract FactoryMẫu ObserverMẫu Strategy2Tìm hiểu chuyên sâu về kiểu dữ liệu và struct
Kiểu cơ bản và kiểu tổng hợpĐịnh nghĩa kiểu dữ liệu tùy chỉnhThẻ structStruct ẩn danhStruct lồng nhauGiá trị zero và giá trị mặc địnhÔn tập: sổ liên lạc5Ưu tiên kết hợp hơn kế thừa
Vì sao Go không có kế thừaNhập môn nhúng structTự động nâng phương thứcNhúng nhiều structNhúng và kết tậpChe khuất phương thức được nhúngÔn tập: hệ phân cấp nhân viên8Xử lý lỗi và lập trình hướng đối tượng
Giao diện errorCác kiểu lỗi tùy chỉnhBọc lỗi với fmt.ErrorfLỗi sentinelerrors.Is() và errors.As()Panic, defer và recoverÔn tập: trình phân tích tệp3Con trỏ và bộ nhớ
Cơ bản về con trỏ trong GoCon trỏ đến structTruyền theo giá trị và tham chiếuHàm new()Thu gom rác trong GoÔn tập: xây dựng danh sách liên kết6Đa hình trong Go
Đa hình qua interfaceĐịnh kiểu vịt trong GoQuy tắc thỏa mãn interfaceCác tập hợp đa hìnhTiêm phụ thuộcÔn tập: bộ xử lý thanh toán9Lập trình đồng thời và lập trình hướng đối tượng
Cơ bản về goroutineChannel và giao tiếpChannel có bộ đệm và không có bộ đệmCâu lệnh selectsync.Mutex và sync.RWMutexsync.WaitGroupThiết kế struct an toàn luồngÔn tập: worker poolTự luyện tập: Trình biên dịch Go online