System bankowy
Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 106 z 107.
Wyzwanie
ŁatwyZbudujmy 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 interfejsTransactablez metodamiDeposit(amount float64) erroriWithdraw(amount float64) error. Następnie utwórz dwa typy kont implementujące ten interfejs:CheckingAccountz nieeksportowanymi polamiid,holderibalance. Konta rozliczeniowe pozwalają na wypłaty, które obniżają saldo dokładnie do zera, ale nie poniżej tej wartości.SavingsAccountz tymi samymi nieeksportowanymi polami oraz polemminBalance. 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 (
NewCheckingAccountiNewSavingsAccount) oraz metody pobierającej saldoBalance() 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ęTransactiondo rejestrowania aktywności na koncie. Każda transakcja zawieraType(string: "deposit" lub "withdrawal"),Amount(float64) iAccountID(string). Dodaj konstruktorNewTransactionoraz 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 kolekcjeAddAccount(id string, account Transactable): rejestruje kontoDeposit(accountID string, amount float64) error: wpłaca środki na konto i rejestruje transakcję w przypadku powodzeniaWithdraw(accountID string, amount float64) error: wypłaca środki z konta i rejestruje transakcję w przypadku powodzeniaTransfer(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 istniejeErrInsufficientFunds: gdy wypłata przekracza dostępne saldoErrInvalidAmount: gdy kwota wpłaty lub wypłaty wynosi zero albo jest ujemnaErrMinBalanceRequired: 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 (checkinglubsavings), 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:
OKw przypadku udanych wpłat, wypłat lub przelewów, saldo sformatowane do dwóch miejsc po przecinku w przypadku zapytań o saldo alboError: [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 ACC002Wynik powinien być następujący:
OK
700.00
Error: minimum balance required
Error: insufficient funds
OK
1000.00A dla danych:
1
checking
C100
Charlie
100.00
4
withdraw C100 100
balance C100
deposit C100 -50
withdraw C999 50Wynik powinien być następujący:
OK
0.00
Error: invalid amount
Error: account not foundZwróć 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
1Podstawy OOP w Go
Pliki zewnętrzneObszar roboczy i moduły GoPakiety i importyNazwy eksportowane i nieeksportowaneWprowadzenie do OOP w GoStruktury jako klasyDefiniowanie metod dla strukturOdbiorniki wskaźnikowe i wartościoweInicjalizacja strukturFunkcje konstruktoraPodsumowanie – prosty kalkulator4Interfejsy
Wprowadzenie do interfejsówImplementacja niejawnaInterfejs jako kontraktPusty interfejs (any)Asercja typuPrzełącznik typówKompozycja interfejsówInterfejsy Stringer i ErrorPowtórka — kalkulator figur7Hermetyzacja
Pola eksportowane i nieeksportowaneHermetyzacja na poziomie pakietuMetody pobierające i ustawiająceUkrywanie informacji w GoPowtórka – dane studentów10Typy generyczne (Go 1.18+)
Wprowadzenie do typów generycznychParametry typówOgraniczenia typówStruktury generyczneObejście ograniczeń metod generycznychPodsumowanie — generyczna kolekcja2Typy i struktury w praktyce
Typy proste i złożoneDefiniowanie własnych typówTagi pól strukturStruktury anonimoweStruktury zagnieżdżoneWartości zerowe i domyślnePodsumowanie – książka kontaktów5Kompozycja zamiast dziedziczenia
Dlaczego Go nie ma dziedziczeniaPodstawy osadzania strukturPromowanie metodOsadzanie wielu strukturOsadzanie a agregacjaPrzesłanianie osadzonych metodPodsumowanie — hierarchia pracowników8Obsługa błędów i programowanie obiektowe
Interfejs błęduWłasne typy błędówOpakowywanie błędów (fmt.Errorf)Błędy wartowniczeerrors.Is() i errors.As()Panic, Defer i RecoverPodsumowanie — parser plików3Wskaźniki i pamięć
Podstawy wskaźników w GoWskaźniki do strukturPrzekazywanie przez wartość a przez referencjęFunkcja new()Odśmiecanie pamięci w GoPowtórka — kreator listy wiązanej6Polimorfizm w Go
Polimorfizm za pomocą interfejsówDuck typing w GoZasady spełniania interfejsówKolekcje polimorficzneWstrzykiwanie zależnościPowtórka — procesor płatności9Współbieżność i OOP
Podstawy goroutinesKanały i komunikacjaKanały buforowane i niebuforowaneInstrukcja selectsync.Mutex i sync.RWMutexsync.WaitGroupProjektowanie struktur bezpiecznych wątkowoPowtórka – pula workerów12Zaawansowane zagadnienia programowania obiektowego
Wzorzec opcji funkcyjnychWzorzec Builder w GoŁańcuchowe wywoływanie metodAliasy typów a definicje typówPodstawy refleksjiPrzegląd generowania koduPoćwicz samodzielnie: Kompilator Go online