Ukrywanie informacji w Go
Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 50 z 107.
Ukrywanie informacji to coś więcej niż tylko oznaczanie pól jako nieeksportowane. Chodzi o projektowanie typów w taki sposób, aby kod zewnętrzny wiedział tylko tyle, ile musi. Nic więcej.
Celem jest udostępnienie zachowania za pomocą metod przy jednoczesnym całkowitym ukryciu wewnętrznej struktury. Rozważ takie podejście:
type Counter struct {
value int
maxLimit int
}
func NewCounter(limit int) *Counter {
return &Counter{maxLimit: limit}
}
func (c *Counter) Increment() bool {
if c.value >= c.maxLimit {
return false
}
c.value++
return true
}
func (c *Counter) Value() int {
return c.value
}
Zewnętrzny kod nie wie, że Counter wewnętrznie używa int. Może to być float64, wycinek lub coś zupełnie innego. Implementację można zmienić bez wpływu na jakikolwiek kod korzystający z Counter, o ile metody zachowują się tak samo.
Ta zasada dotyczy również funkcji pomocniczych. Nie eksportuj logiki wewnętrznej:
// Eksportowana - część publicznego API
func (c *Counter) Reset() {
c.value = 0
}
// nieeksportowana - wewnętrzna funkcja pomocnicza
func (c *Counter) isAtLimit() bool {
return c.value >= c.maxLimit
}
Ukrywając isAtLimit(), możesz później swobodnie ją zmienić lub usunąć. Ukrywanie informacji tworzy wyraźną granicę między tym, co Twój pakiet obiecuje użytkownikom zewnętrznym, a tym, jak działa wewnętrznie.
Wyzwanie
ŁatwyZbudujmy bezpieczny system portfela, który demonstruje ukrywanie informacji. Utworzysz portfel cyfrowy, w którym wewnętrzne śledzenie salda i historia transakcji są całkowicie ukryte przed zewnętrznym kodem: udostępniane jest wyłącznie zachowanie poprzez metody.
Rozmieścisz kod w dwóch plikach:
wallet.go: Utwórz strukturęWallet, która ukrywa cały swój wewnętrzny stan. Portfel powinien wewnętrznie śledzić bieżące saldo i limit wydatków, ale zewnętrzny kod nie powinien wiedzieć, jak są one przechowywane ani reprezentowane. Udostępnij zachowanie za pomocą tych metod:NewWallet(limit float64) *Wallet— konstruktor, który tworzy portfel z podanym limitem wydatków i zerowym saldemDeposit(amount float64) bool— dodaje pieniądze do portfela, zwracatrue, jeśli operacja się powiedzie (kwota musi być dodatnia)Spend(amount float64) bool— odejmuje pieniądze, jeśli saldo jest wystarczające ORAZ kwota nie przekracza limitu wydatków; zwracatrue, jeśli operacja się powiedzieBalance() float64— zwraca bieżące saldoStatus() string— zwraca komunikat o stanie w formacie:Balance: $[balance] (Limit: $[limit])
main.go: Odczytaj konfigurację portfela i transakcje z wejścia, utwórz portfel, wykonaj operacje i wyświetl wyniki po każdej z nich.
Dane wejściowe będą następujące:
- Wiersz 1: Limit wydatków
- Wiersz 2: Kwota wpłaty
- Wiersz 3: Kwota pierwszej wypłaty
- Wiersz 4: Kwota drugiej wypłaty
Po utworzeniu portfela wypisz jego początkowy stan. Następnie wykonaj każdą operację i wypisz zaktualizowany stan (jeśli operacja się powiedzie) albo Transaction failed (jeśli operacja zostanie odrzucona). Wszystkie kwoty w dolarach formatuj z dokładnością do dwóch miejsc po przecinku.
Na przykład dla danych 50, 100, 30 i 80 wynik powinien wyglądać tak:
Balance: $0.00 (Limit: $50.00)
Balance: $100.00 (Limit: $50.00)
Balance: $70.00 (Limit: $50.00)
Transaction failedOstatnia transakcja nie powiedzie się, ponieważ kwota $80 przekracza limit wydatków wynoszący $50, mimo że saldo jest wystarczające. Portfel wewnętrznie egzekwuje swoje zasady, nie ujawniając, w jaki sposób podejmuje te decyzje. Zewnętrzny kod po prostu wywołuje metody i otrzymuje wyniki, nie poznając wewnętrznych szczegółów implementacji.
Spróbuj swoich sił
package main
import (
"fmt"
)
func main() {
// Wczytaj dane wejściowe
var limit float64
var depositAmount float64
var spend1 float64
var spend2 float64
fmt.Scanln(&limit)
fmt.Scanln(&depositAmount)
fmt.Scanln(&spend1)
fmt.Scanln(&spend2)
// TODO: Utwórz nowy portfel z podanym limitem wydatków
// TODO: Wyświetl stan początkowy
// TODO: Wpłać środki i wyświetl wynik (stan lub "Transaction failed")
// TODO: Wykonaj pierwszy wydatek i wyświetl wynik
// TODO: Wykonaj drugi wydatek i wyświetl wynik
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
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