Menu
CoddyTech

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.

challenge icon

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ện Transactable với các phương thức Deposit(amount float64) error và Withdraw(amount float64) error. Sau đó tạo hai kiểu tài khoản triển khai giao diện này:

    Một CheckingAccount có các trường chưa được xuất id, holder và 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 SavingsAccount có các trường chưa được xuất giống như trên, cùng với trường minBalance. 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 (NewCheckingAccount và NewSavingsAccount) cùng phương thức getter Balance() 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úc Transaction để 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ạo NewTransaction và phương thức String() string trả 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úc Bank để 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ỗng
    • AddAccount(id string, account Transactable): đăng ký một tài khoản
    • Deposit(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ông
    • Withdraw(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ông
    • Transfer(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ại
    • ErrInsufficientFunds: 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 âm
    • ErrMinBalanceRequired: 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 (checking hoặc savings), 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ả: OK nế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ặc Error: [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 ACC002

Kết quả đầu ra sẽ là:

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

Với đầu vào:

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

Kết quả đầu ra sẽ là:

OK
0.00
Error: invalid amount
Error: account not found

Lư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

Tự luyện tập: Trình biên dịch Go online