Menu
Coddy logo textTech

System bankowy

Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 106 z 107.

challenge icon

Wyzwanie

Łatwy

Zbudujmy system bankowy, który zarządza kontami, transakcjami i operacjami finansowymi! Stworzysz solidny system, w którym integralność danych ma kluczowe znaczenie: salda kont powinny zmieniać się wyłącznie za pomocą metod przeprowadzających walidację, a błędy, takie jak przekroczenie dostępnych środków lub nieprawidłowe kwoty, muszą być obsługiwane w odpowiedni sposób.

Rozmieścisz kod w pięciu plikach:

  • account.go: Zdefiniuj interfejs Transactable z metodami Deposit(amount float64) error i Withdraw(amount float64) error. Następnie utwórz dwa typy kont implementujące ten interfejs:

    CheckingAccount z nieeksportowanymi polami id, holder i balance. Konta rozliczeniowe pozwalają na wypłaty, które obniżają saldo dokładnie do zera, ale nie poniżej tej wartości.

    SavingsAccount z tymi samymi nieeksportowanymi polami oraz polem minBalance. Konta oszczędnościowe muszą utrzymywać minimalne saldo w wysokości 100.0: wypłaty, które obniżyłyby saldo poniżej tego progu, powinny zakończyć się niepowodzeniem.

    Oba typy kont potrzebują konstruktorów (NewCheckingAccount i NewSavingsAccount) oraz metody pobierającej saldo Balance() float64. Odrzucaj ujemne lub zerowe kwoty wpłat. W przypadku wypłat odrzucaj ujemne lub zerowe kwoty oraz kwoty przekraczające dostępne środki.

  • transaction.go: Utwórz strukturę Transaction do rejestrowania aktywności na koncie. Każda transakcja zawiera Type (string: "deposit" lub "withdrawal"), Amount (float64) i AccountID (string). Dodaj konstruktor NewTransaction oraz metodę String() string, która zwraca format: [Type] $[Amount] on [AccountID], z kwotą sformatowaną do dwóch miejsc po przecinku.
  • bank.go: Zbuduj strukturę Bank, która zarządza wieloma kontami i rejestruje transakcje. Przechowuj konta w mapie według ich ID oraz przechowuj transakcje w wycinku. Zaimplementuj:
    • NewBank() *Bank: inicjuje puste kolekcje
    • AddAccount(id string, account Transactable): rejestruje konto
    • Deposit(accountID string, amount float64) error: wpłaca środki na konto i rejestruje transakcję w przypadku powodzenia
    • Withdraw(accountID string, amount float64) error: wypłaca środki z konta i rejestruje transakcję w przypadku powodzenia
    • Transfer(fromID, toID string, amount float64) error: przenosi środki między kontami w sposób atomowy (jeśli wypłata się powiedzie, ale wpłata zakończy się niepowodzeniem, cofnij wypłatę)
    • GetBalance(accountID string) (float64, error): zwraca saldo konta

    Zwracaj odpowiednie błędy, gdy konto nie istnieje.

  • errors.go: Zdefiniuj błędy-sentinels dla operacji bankowych:
    • ErrAccountNotFound: gdy konto o podanym ID nie istnieje
    • ErrInsufficientFunds: gdy wypłata przekracza dostępne saldo
    • ErrInvalidAmount: gdy kwota wpłaty lub wypłaty wynosi zero albo jest ujemna
    • ErrMinBalanceRequired: gdy wypłata z konta oszczędnościowego naruszyłaby minimalne saldo
  • main.go: Utwórz bank i przetwarzaj operacje na podstawie danych wejściowych. Wczytaj liczbę kont do utworzenia. Dla każdego konta wczytaj typ konta (checking lub savings), ID, imię właściciela i saldo początkowe. Utwórz odpowiedni typ konta i dodaj go do banku.

    Następnie wczytaj liczbę operacji. Każda operacja jest jednym z poniższych poleceń:

    • deposit [accountID] [amount]
    • withdraw [accountID] [amount]
    • transfer [fromID] [toID] [amount]
    • balance [accountID]

    Dla każdej operacji wypisz wynik: OK w przypadku udanych wpłat, wypłat lub przelewów, saldo sformatowane do dwóch miejsc po przecinku w przypadku zapytań o saldo albo Error: [message] w przypadku błędów, używając komunikatów błędów-sentinel.

Podane zostaną następujące dane wejściowe:

  • Liczba kont, a następnie typ, ID, właściciel i saldo początkowe każdego konta (po 4 wiersze na konto)
  • Liczba operacji, a następnie każda operacja (po 1 wierszu na operację)

Na przykład dla danych:

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

Wynik powinien być następujący:

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

A dla danych:

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

Wynik powinien być następujący:

OK
0.00
Error: invalid amount
Error: account not found

Zwróć uwagę, że interfejs Transactable pozwala bankowi jednolicie obsługiwać oba typy kont, a każdy typ konta egzekwuje własne reguły. Metoda Transfer pokazuje operacje atomowe: jeśli coś pójdzie nie tak w trakcie, system zachowa spójność!

Spróbuj swoich sił

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)

	// Odczytaj liczbę kont
	scanner.Scan()
	numAccounts, _ := strconv.Atoi(scanner.Text())

	// Utwórz nowy bank
	bank := NewBank()

	// Odczytaj dane kont i utwórz konta
	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: Utwórz odpowiedni typ konta na podstawie accountType
		// i dodaj je do banku
		_ = accountType
		_ = id
		_ = holder
		_ = balance
	}

	// Odczytaj liczbę operacji
	scanner.Scan()
	numOps, _ := strconv.Atoi(scanner.Text())

	// Przetwórz każdą operację
	for i := 0; i < numOps; i++ {
		scanner.Scan()
		parts := strings.Fields(scanner.Text())
		operation := parts[0]

		// TODO: Obsłuż każdy typ operacji:
		// - "deposit": wpłać środki na konto, wypisz "OK" lub błąd
		// - "withdraw": wypłać środki z konta, wypisz "OK" lub błąd
		// - "transfer": przelej środki między kontami, wypisz "OK" lub błąd
		// - "balance": pobierz saldo i wypisz je z dokładnością do 2 miejsc po przecinku
		_ = operation
	}
}

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Go online