Information Hiding in Go
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 50 von 107.
Information Hiding geht über das bloße Unexportiert-Machen von Feldern hinaus. Es geht darum, Ihre Typen so zu entwerfen, dass externer Code nur das erfährt, was er wissen muss – nicht mehr.
Das Ziel ist es, Verhalten durch Methoden offenzulegen, während die interne Struktur vollständig verborgen bleibt. Betrachten Sie 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 sich ändern, ohne den Code zu beeinflussen, der Counter verwendet – solange sich die Methoden auf die gleiche Weise verhalten.
Dieses Prinzip gilt auch für Hilfsfunktionen. Halten Sie die interne Logik nicht exportiert:
// Exportiert - Teil der öffentlichen API
func (c *Counter) Reset() {
c.value = 0
}
// nicht exportiert - interne Hilfsfunktion
func (c *Counter) isAtLimit() bool {
return c.value >= c.maxLimit
}
Indem Sie isAtLimit() verbergen, können Sie es später frei ändern oder entfernen. Information Hiding schafft eine klare Trennung zwischen dem, was Ihr Paket externen Benutzern verspricht, und wie es intern funktioniert.
Aufgabe
EinfachLassen Sie uns ein sicheres Wallet-System bauen, das Information Hiding demonstriert. Sie werden ein digitales Wallet erstellen, bei dem die interne Kontostandsverfolgung und der Transaktionsverlauf vollständig vor externem Code verborgen sind – nur das Verhalten wird über Methoden offengelegt.
Sie werden Ihren Code in zwei Dateien organisieren:
wallet.go: Erstellen Sie einWallet-Struct, das seinen gesamten internen Zustand verbirgt. Das Wallet sollte den aktuellen Kontostand und ein Ausgabenlimit intern verfolgen, aber externer Code sollte niemals wissen, wie diese gespeichert oder dargestellt werden. Legen Sie das Verhalten über diese Methoden offen:NewWallet(limit float64) *Wallet- Konstruktor, der ein Wallet mit dem angegebenen Ausgabenlimit und einem Kontostand von Null erstelltDeposit(amount float64) bool- fügt dem Wallet Geld hinzu, gibttruezurück, wenn erfolgreich (Betrag muss positiv sein)Spend(amount float64) bool- entfernt Geld, wenn ausreichend Guthaben vorhanden ist UND der Betrag das Ausgabenlimit nicht überschreitet, gibttruezurück, wenn erfolgreichBalance() float64- gibt den aktuellen Kontostand zurückStatus() string- gibt eine Statusmeldung im Format:Balance: $[balance] (Limit: $[limit])zurück
main.go: Lesen Sie die Wallet-Konfiguration und Transaktionen aus der Eingabe, erstellen Sie ein Wallet, führen Sie Operationen durch und zeigen Sie die Ergebnisse nach jeder Aktion an.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Ausgabenlimit
- Zeile 2: Einzahlungsbetrag
- Zeile 3: Erster Ausgabenbetrag
- Zeile 4: Zweiter Ausgabenbetrag
Geben Sie nach dem Erstellen des Wallets seinen ursprünglichen Status aus. Führen Sie dann jede Operation aus und geben Sie entweder den aktualisierten Status (bei Erfolg) oder Transaction failed (wenn die Operation abgelehnt wurde) aus. Formatieren Sie alle Dollarbeträge mit zwei Dezimalstellen.
Zum Beispiel, gegeben 50, 100, 30 und 80, sollte Ihre Ausgabe wie folgt 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, da $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.
Spickzettel
Information Hiding bedeutet, Typen so zu entwerfen, dass externer Code nur das erfährt, was er wissen muss. Exponieren Sie Verhalten durch Methoden, während die interne Struktur vollständig verborgen bleibt.
Beispiel für einen Typ mit verborgener Implementierung:
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 kennt die interne Repräsentation nicht (es könnte int, float64 oder etwas anderes sein). Die Implementierung kann sich ändern, ohne den Code zu beeinflussen, der sie verwendet.
Halten Sie Hilfsfunktionen unexportiert:
// Exported - part of public API
func (c *Counter) Reset() {
c.value = 0
}
// unexported - internal helper
func (c *Counter) isAtLimit() bool {
return c.value >= c.maxLimit
}
Information Hiding schafft eine klare Grenze zwischen dem, was Ihr Paket nach außen verspricht, und wie es intern funktioniert.
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 angegebenen Ausgabenlimit
// TODO: Initialen Status ausgeben
// TODO: Einzahlung durchführen und Ergebnis ausgeben (Status oder "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 Go OOP
Externe DateienGo Workspace & ModulePackages & ImportsExportierte vs. nicht-exportierte NamenEinführung in OOP in GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktor-FunktionenZusammenfassung – Einfacher Taschenrechner4Interfaces
Einführung in InterfacesImplizite ImplementierungInterface als VertragLeeres Interface (any)Type AssertionType SwitchInterface-KompositionStringer & Error InterfacesRückblick - Formen-Rechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf Paket-EbeneGetter- & Setter-MethodenInformation Hiding in GoZusammenfassung - Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterType ConstraintsGenerische StructsWorkaround für generische MethodenZusammenfassung – Generische Collection2Vertiefung: Typen & Structs
Basis- & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct TagsAnonyme StructsVerschachtelte StructsZero Values & StandardwerteZusammenfassung - Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen des Struct EmbeddingMethod PromotionEinbetten mehrerer StructsEmbedding vs. AggregationShadowing eingebetteter MethodenZusammenfassung – Mitarbeiter-Hierarchie8Fehlerbehandlung & OOP
Das error-InterfaceEigene FehlertypenError Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverZusammenfassung – Datei-Parser3Zeiger & Speicher
Zeiger-Grundlagen in GoZeiger auf StructsWertübergabe vs. ReferenzübergabeDie new() FunktionGarbage Collection in GoWiederholung – Linked List Builder6Polymorphismus in Go
Polymorphismus über InterfacesDuck Typing in GoInterface-ErfüllungsregelnPolymorphe CollectionsDependency InjectionZusammenfassung – Payment Processor9Nebenläufigkeit & OOP
Goroutines GrundlagenChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignZusammenfassung - Worker Pool