Informationsverbergung in Go
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 50 von 107.
Informationsverbergen geht über das bloße Ausblenden von Feldern hinaus. Es geht darum, deine Typen so zu entwerfen, dass externer Code nur weiß, was er wissen muss. Nicht mehr.
Das Ziel besteht darin, Verhalten über Methoden offenzulegen und dabei die interne Struktur vollständig verborgen zu halten. Betrachte diesen Ansatz:
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
}
Externer Code weiß nicht, dass Counter intern ein int verwendet. Es könnte ein float64, ein Slice oder etwas völlig anderes sein. Die Implementierung kann geändert werden, ohne dass irgendein Code betroffen ist, der Counter verwendet, solange sich die Methoden gleich verhalten.
Dieses Prinzip gilt auch für Hilfsfunktionen. Halte die interne Logik nicht exportiert:
// Exportiert - Teil der öffentlichen API
func (c *Counter) Reset() {
c.value = 0
}
// unexportiert - interner Helfer
func (c *Counter) isAtLimit() bool {
return c.value >= c.maxLimit
}
Indem du isAtLimit() verbirgst, kannst du es später problemlos ändern oder entfernen. Informationsverbergung schafft eine klare Grenze zwischen dem, was dein Paket externen Nutzern verspricht, und der internen Funktionsweise.
Aufgabe
EinfachLass uns ein sicheres Wallet-System entwickeln, das Informationsverbergung demonstriert. Du erstellst ein digitales Wallet, bei dem die interne Verwaltung des Guthabens und der Transaktionsverlauf vollständig vor externem Code verborgen sind: Über Methoden wird nur das Verhalten offengelegt.
Du organisierst deinen Code über zwei Dateien:
wallet.go: Erstelle eineWallet-Struktur, die ihren gesamten internen Zustand verbirgt. Das Wallet soll das aktuelle Guthaben und ein Ausgabenlimit intern verwalten, aber externer Code soll niemals wissen, wie diese gespeichert oder dargestellt werden. Lege das Verhalten über diese Methoden offen:NewWallet(limit float64) *Wallet– Konstruktor, der ein Wallet mit dem angegebenen Ausgabenlimit und einem Guthaben von null erstelltDeposit(amount float64) bool– fügt dem Wallet Geld hinzu und gibttruezurück, wenn der Vorgang erfolgreich war (der Betrag muss positiv sein)Spend(amount float64) bool– entfernt Geld, wenn ein ausreichendes Guthaben vorhanden ist UND der Betrag das Ausgabenlimit nicht überschreitet, und gibttruezurück, wenn der Vorgang erfolgreich warBalance() float64– gibt das aktuelle Guthaben zurückStatus() string– gibt eine Statusmeldung im FormatBalance: $[balance] (Limit: $[limit])zurück
main.go: Lies die Wallet-Konfiguration und die Transaktionen aus der Eingabe, erstelle ein Wallet, führe die Vorgänge aus und zeige nach jeder Aktion die Ergebnisse an.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Ausgabenlimit
- Zeile 2: Einzahlungsbetrag
- Zeile 3: Erster Ausgabebetrag
- Zeile 4: Zweiter Ausgabebetrag
Gib nach dem Erstellen des Wallets zunächst dessen Status aus. Führe dann jeden Vorgang aus und gib entweder den aktualisierten Status (wenn der Vorgang erfolgreich war) oder Transaction failed (wenn der Vorgang abgelehnt wurde) aus. Formatiere alle Dollarbeträge mit zwei Nachkommastellen.
Wenn beispielsweise 50, 100, 30 und 80 angegeben werden, sollte deine Ausgabe folgendermaßen aussehen:
Balance: $0.00 (Limit: $50.00)
Balance: $100.00 (Limit: $50.00)
Balance: $70.00 (Limit: $50.00)
Transaction failedDie letzte Transaktion schlägt fehl, weil $80 das Ausgabenlimit von $50 überschreitet, obwohl genügend Guthaben vorhanden ist. Das Wallet setzt seine Regeln intern durch, ohne offenzulegen, wie es diese Entscheidungen trifft. Externer Code ruft einfach Methoden auf und erhält Ergebnisse, ohne jemals die internen Implementierungsdetails zu kennen.
Probier es selbst
package main
import (
"fmt"
)
func main() {
// Eingaben lesen
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: Erstelle eine neue Wallet mit dem gegebenen Ausgabenlimit
// TODO: Initialen Status ausgeben
// TODO: Perform deposit and print result (status or "Transaction failed")
// TODO: Erste Ausgabe durchführen und Ergebnis ausgeben
// TODO: Zweite Ausgabe durchführen und Ergebnis ausgeben
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP in Go
Externe DateienGo-Workspace & ModulePackages & ImportsExportierte vs. nicht exportierte NamenEinführung in OOP mit GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktorfunktionenRückblick – Einfacher Taschenrechner4Schnittstellen
Einführung in SchnittstellenImplizite ImplementierungSchnittstelle als VertragLeere Schnittstelle (any)TypzusicherungTypwechselZusammensetzung von SchnittstellenStringer- und Error-SchnittstellenRückblick – Formenrechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf PaketebeneGetter- und Setter-MethodenInformationsverbergung in GoRückblick – Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterTypbeschränkungenGenerische StrukturenWorkaround für generische MethodenZusammenfassung – Generische Sammlung2Typen & Structs im Detail
Grundlegende & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct-TagsAnonyme StructsVerschachtelte StructsNullwerte & StandardwerteRückblick – Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen der Struct-EinbettungMethoden-PromotionMehrere Structs einbettenEinbettung vs. AggregationVerbergen eingebetteter MethodenRückblick – Mitarbeiterhierarchie8Fehlerbehandlung & OOP
Das Error-InterfaceBenutzerdefinierte FehlertypenError-Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverRückblick – Dateiparser3Zeiger & Speicher
Grundlagen von Zeigern in GoZeiger auf StructsÜbergabe per Wert vs. ReferenzDie Funktion new()Garbage Collection in GoRückblick – Verkettete Liste erstellen6Polymorphismus in Go
Polymorphismus über InterfacesDuck-Typing in GoRegeln zur Interface-ErfüllungPolymorphe SammlungenDependency InjectionZusammenfassung – Zahlungsprozessor9Konkurrenz & OOP
Grundlagen der GoroutinesChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignRückblick – Worker PoolÜbe selbstständig: Online-Go-Compiler