Kapselung auf Paketebene
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 48 von 107.
Während die vorherige Lektion die Sichtbarkeit auf Feldebene behandelt hat, funktioniert die Kapselung in Go tatsächlich auf Paketebene. Alles innerhalb desselben Pakets kann unabhängig davon, in welcher Datei es sich befindet, auf nicht exportierte Felder und Funktionen zugreifen.
Stell dir ein Paket mit mehreren Dateien vor:
// 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-Paket, daher kann helper.go direkt auf das nicht exportierte balance-field zugreifen. Das ist beabsichtigt. Dadurch kannst du die Paketlogik auf mehrere Dateien aufteilen und gleichzeitig den internen Zugriff beibehalten.
Code außerhalb des Pakets kann jedoch 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
// account.ResetAccount // funktioniert, wenn ResetAccount exportiert wäre
}
Diese Grenze auf Paketebene ist Gos wichtigster Mechanismus zur Kapselung. Du entwirfst Pakete als zusammenhängende Einheiten, in denen der interne Code frei zusammenarbeitet, während externer Code nur über die exportierte API interagiert. Betrachte jedes Paket als ein eigenständiges Modul mit einer klaren öffentlichen Schnittstelle.
Aufgabe
EinfachErstellen wir ein Inventarverwaltungssystem, 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.
Du erstellst drei Dateien, die im Paket main zusammenarbeiten:
product.go: Definiere eineProduct-Struktur mit einem exportierten FeldNameund einem nicht exportierten Feldquantity(int). Füge eine exportierte MethodeInfo() stringhinzu, die das folgende Format zurückgibt:[Name]: [quantity] in stockinventory.go: Erstelle Hilfsfunktionen, die mit Produkten arbeiten, indem sie direkt auf das nicht exportierte Feldquantityzugreifen (was möglich ist, weil sie sich im selben Paket befinden). Implementiere:Restock(p *Product, amount int)– addiert den Betrag zur Menge des ProduktsSell(p *Product, amount int) bool– zieht den Betrag von der Menge ab, wenn ausreichend Bestand vorhanden ist, und gibttruezurück, wenn der Vorgang erfolgreich war, oderfalse, wenn der Bestand nicht ausreichtNewProduct(name string, initialQty int) *Product– Konstruktor, der ein Produkt mit dem angegebenen Namen und der anfänglichen Menge erstellt
main.go: Lies die Produktdetails aus der Eingabe, erstelle mithilfe des Konstruktors ein Produkt, führe Inventaroperationen aus und zeige die Ergebnisse an. Gib nach jeder Operation die Produktinformationen aus, um den aktualisierten Zustand anzuzeigen.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Produktname
- Zeile 2: Anfängliche Menge (Ganzzahl)
- Zeile 3: Nachschubmenge (Ganzzahl)
- Zeile 4: Verkaufsmenge (Ganzzahl)
Gib nach dem Erstellen des Produkts zunächst dessen Anfangsinformationen aus. Führe dann den Nachschub durch und gib die Informationen erneut aus. Versuche schließlich, das Produkt zu verkaufen, und gib entweder die aktualisierten Informationen (bei Erfolg) oder Sale failed: insufficient stock aus (wenn die Menge nicht ausreicht).
Wenn beispielsweise Laptop, 10, 5 und 8 angegeben werden, sollte deine Ausgabe folgendermaßen aussehen:
Laptop: 10 in stock
Laptop: 15 in stock
Laptop: 7 in stockUnd wenn Phone, 3, 2 und 10 angegeben werden, sollte deine Ausgabe folgendermaßen aussehen:
Phone: 3 in stock
Phone: 5 in stock
Sale failed: insufficient stockDie wichtigste Erkenntnis ist, dass inventory.go das nicht exportierte Feld quantity direkt lesen und ändern kann, weil es sich im selben Paket wie product.go befindet. Dies ist Kapselung auf Paketebene in der Praxis: Die interne Zusammenarbeit findet uneingeschränkt statt, während die Daten vor externen Paketen geschützt bleiben.
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Produktname 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: Erstelle ein neues Produkt mit dem NewProduct-Konstruktor
// TODO: Gib die initialen Produktinformationen aus
// TODO: Fülle das Produkt nach und gib die Informationen aus
// TODO: Versuche zu verkaufen und gib das Ergebnis aus
// Wenn der Verkauf erfolgreich ist, gib die aktualisierten Informationen aus
// If sale fails, print "Sale failed: insufficient stock"
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 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