Kapselung auf Paket-Ebene
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 48 von 107.
Während die vorherige Lektion die Sichtbarkeit auf Feldebene behandelte, findet die Kapselung in Go tatsächlich auf der Paketebene statt. Alles innerhalb desselben Pakets – unabhängig davon, in welcher Datei es sich befindet – kann auf nicht exportierte Felder und Funktionen zugreifen.
Betrachten Sie ein Paket mit mehreren Dateien:
// account/account.go
package account
type Account struct {
balance int // nicht exportiert
}
func (a *Account) Deposit(amount int) {
a.balance += amount
}
// account/helper.go
package account
func ResetAccount(a *Account) {
a.balance = 0 // funktioniert - gleiches Paket
}
Beide Dateien gehören zum account-Package, sodass helper.go direkt auf das nicht exportierte balance-Feld zugreifen kann. Dies ist beabsichtigt – es ermöglicht Ihnen, die Package-Logik auf mehrere Dateien aufzuteilen und gleichzeitig den internen Zugriff beizubehalten.
Code außerhalb des Pakets kann jedoch nicht auf nicht-exportierte Member zugreifen:
// main.go
package main
import "account"
func main() {
acc := &account.Account{}
acc.Deposit(100) // funktioniert - exportierte Methode
// acc.balance = 50 // Fehler - nicht-exportiertes Feld
// account.ResetAccount // funktioniert, wenn ResetAccount exportiert wäre
}
Diese Grenze auf Paketebene ist der primäre Kapselungsmechanismus von Go. Sie entwerfen Pakete als zusammenhängende Einheiten, in denen interner Code frei zusammenarbeitet, während externer Code nur über die exportierte API interagiert. Betrachten Sie jedes Paket als ein eigenständiges Modul mit einer klaren öffentlichen Schnittstelle.
Aufgabe
EinfachLassen Sie uns ein Bestandsverwaltungssystem erstellen, das zeigt, wie mehrere Dateien innerhalb desselben Pakets zusammenarbeiten können, indem sie direkt auf nicht exportierte Felder zugreifen, während diese Daten vor externem Code verborgen bleiben.
Sie werden drei Dateien erstellen, die im main-Paket zusammenarbeiten:
product.go: Definieren Sie einProduct-Struct mit einem exportiertenName-Feld und einem nicht exportiertenquantity-Feld (int). Fügen Sie eine exportierte MethodeInfo() stringhinzu, die das Format[Name]: [quantity] in stockzurückgibt.inventory.go: Erstellen Sie Hilfsfunktionen, die mit Produkten arbeiten, indem sie direkt auf das nicht exportiertequantity-Feld zugreifen (möglich, da sie sich im selben Paket befinden). Implementieren Sie:Restock(p *Product, amount int)- fügt den Betrag zur Menge des Produkts hinzuSell(p *Product, amount int) bool- subtrahiert den Betrag von der Menge, wenn genügend Bestand vorhanden ist; gibttruezurück, wenn erfolgreich, oderfalse, wenn nicht genügend Bestand vorhanden istNewProduct(name string, initialQty int) *Product- Konstruktor, der ein Produkt mit dem angegebenen Namen und der Anfangsmenge erstellt
main.go: Lesen Sie Produktdetails aus der Eingabe, erstellen Sie ein Produkt mit dem Konstruktor, führen Sie Bestandsoperationen durch und zeigen Sie die Ergebnisse an. Geben Sie nach jeder Operation die Produktinfo aus, um den aktualisierten Zustand anzuzeigen.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Produktname
- Zeile 2: Anfangsmenge (Ganzzahl)
- Zeile 3: Nachfüllmenge (Ganzzahl)
- Zeile 4: Verkaufsmenge (Ganzzahl)
Geben Sie nach dem Erstellen des Produkts dessen Anfangsinfo aus. Füllen Sie dann den Bestand auf und geben Sie die Info erneut aus. Versuchen Sie schließlich zu verkaufen und geben Sie entweder die aktualisierte Info (bei Erfolg) oder "Sale failed: insufficient stock" (bei unzureichender Menge) aus.
Zum Beispiel, bei "Laptop", 10, 5 und 8 sollte Ihre Ausgabe wie folgt aussehen:
Laptop: 10 in stock
Laptop: 15 in stock
Laptop: 7 in stockUnd bei "Phone", 3, 2 und 10 sollte Ihre Ausgabe wie folgt aussehen:
Phone: 3 in stock
Phone: 5 in stock
Sale failed: insufficient stockDie entscheidende Erkenntnis hierbei ist, dass inventory.go das nicht exportierte quantity-Feld direkt lesen und ändern kann, da es sich im selben Paket wie product.go befindet. Dies ist Kapselung auf Paketebene in Aktion – interne Zusammenarbeit geschieht frei, während die Daten vor externen Paketen geschützt bleiben.
Spickzettel
Gos Kapselung erfolgt auf Paketebene, nicht auf Dateiebene. Alle Dateien innerhalb desselben Pakets können auf nicht exportierte Felder und Funktionen zugreifen, unabhängig davon, in welcher Datei sie definiert sind.
Zugriff auf Paketebene
Nicht exportierte Mitglieder sind über alle Dateien im selben Paket hinweg zugänglich:
// account/account.go
package account
type Account struct {
balance int // nicht exportiert
}
func (a *Account) Deposit(amount int) {
a.balance += amount
}
// account/helper.go
package account
func ResetAccount(a *Account) {
a.balance = 0 // funktioniert - gleiches Paket
}
Einschränkungen für externe Pakete
Code außerhalb des Pakets kann nicht auf nicht exportierte Mitglieder zugreifen:
// main.go
package main
import "account"
func main() {
acc := &account.Account{}
acc.Deposit(100) // funktioniert - exportierte Methode
// acc.balance = 50 // Fehler - nicht exportiertes Feld
}
Dieses Design ermöglicht es Ihnen, die Paketlogik auf mehrere Dateien aufzuteilen und gleichzeitig den internen Zugriff beizubehalten, wodurch kohärente Einheiten mit klaren öffentlichen Schnittstellen entstehen.
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Produktnamen lesen
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
// Anfangsmenge lesen
initialQtyStr, _ := reader.ReadString('\n')
initialQty, _ := strconv.Atoi(strings.TrimSpace(initialQtyStr))
// Nachfüllmenge lesen
restockStr, _ := reader.ReadString('\n')
restockAmount, _ := strconv.Atoi(strings.TrimSpace(restockStr))
// Verkaufsmenge lesen
sellStr, _ := reader.ReadString('\n')
sellAmount, _ := strconv.Atoi(strings.TrimSpace(sellStr))
// TODO: Ein neues Produkt mit dem NewProduct-Konstruktor erstellen
// TODO: Initiale Produktinformationen ausgeben
// TODO: Das Produkt auffüllen und Informationen ausgeben
// TODO: Verkauf versuchen und Ergebnis ausgeben
// Wenn der Verkauf erfolgreich ist, die aktualisierten Informationen ausgeben
// Wenn der Verkauf fehlschlägt, "Sale failed: insufficient stock" ausgeben
fmt.Println() // Durch tatsächliche Ausgabe ersetzen
}
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