Hermetyzacja na poziomie pakietu
Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 48 z 107.
Podczas gdy poprzednia lekcja dotyczyła widoczności na poziomie pól, hermetyzacja w Go działa w rzeczywistości na poziomie pakietu. Wszystko w tym samym pakiecie, niezależnie od tego, w którym pliku się znajduje, może uzyskać dostęp do nieeksportowanych pól i funkcji.
Rozważ pakiet zawierający wiele plików:
// account/account.go
package account
type Account struct {
balance int // nieeksportowane
}
func (a *Account) Deposit(amount int) {
a.balance += amount
}
// account/helper.go
package account
func ResetAccount(a *Account) {
a.balance = 0 // działa – ten sam pakiet
}
Oba pliki należą do pakietu account, więc plik helper.go może bezpośrednio uzyskać dostęp do nieeksportowanego pola balance. To celowe. Dzięki temu możesz podzielić logikę pakietu między pliki, zachowując wewnętrzny dostęp.
Jednak kod spoza pakietu nie może uzyskać dostępu do nieeksportowanych elementów:
// main.go
package main
import "account"
func main() {
acc := &account.Account{}
acc.Deposit(100) // działa - metoda eksportowana
// acc.balance = 50 // błąd - pole nieeksportowane
// account.ResetAccount // działałoby, gdyby ResetAccount było eksportowane
}
Ta granica na poziomie pakietu jest podstawowym mechanizmem enkapsulacji w Go. Projektujesz pakiety jako spójne jednostki, w których kod wewnętrzny może swobodnie współpracować, podczas gdy kod zewnętrzny korzysta wyłącznie z eksportowanego API. Potraktuj każdy pakiet jako samodzielny moduł z jasno określonym publicznym interfejsem.
Wyzwanie
ŁatwyZbudujmy system zarządzania zapasami, który pokazuje, jak wiele plików w tym samym pakiecie może współpracować, uzyskując bezpośredni dostęp do nieeksportowanych pól, a jednocześnie ukrywając te dane przed kodem zewnętrznym.
Utworzysz trzy współpracujące ze sobą pliki w pakiecie main:
product.go: Zdefiniuj strukturęProductz eksportowanym polemNamei nieeksportowanym polemquantity(int). Dodaj eksportowaną metodęInfo() string, która zwraca tekst w formacie:[Name]: [quantity] in stockinventory.go: Utwórz funkcje pomocnicze, które działają na produktach, uzyskując bezpośredni dostęp do nieeksportowanego polaquantity(jest to możliwe, ponieważ znajdują się w tym samym pakiecie). Zaimplementuj:Restock(p *Product, amount int)— dodaje podaną ilość do zapasu produktuSell(p *Product, amount int) bool— odejmuje podaną ilość od zapasu, jeśli jest go wystarczająco dużo; zwracatrue, jeśli operacja się powiedzie, albofalse, jeśli zapas jest niewystarczającyNewProduct(name string, initialQty int) *Product— konstruktor tworzący produkt o podanej nazwie i początkowej ilości
main.go: Odczytaj dane produktu z wejścia, utwórz produkt za pomocą konstruktora, wykonaj operacje na zapasach i wyświetl wyniki. Po każdej operacji wypisz informacje o produkcie, aby pokazać jego zaktualizowany stan.
Dane wejściowe będą następujące:
- Wiersz 1: nazwa produktu
- Wiersz 2: początkowa ilość (liczba całkowita)
- Wiersz 3: ilość uzupełnianego zapasu (liczba całkowita)
- Wiersz 4: ilość do sprzedaży (liczba całkowita)
Po utworzeniu produktu wypisz informacje o nim. Następnie uzupełnij zapas i ponownie wypisz informacje. Na koniec spróbuj sprzedać produkt i wypisz zaktualizowane informacje (jeśli operacja się powiedzie) albo Sale failed: insufficient stock (jeśli ilość jest niewystarczająca).
Na przykład dla danych Laptop, 10, 5 i 8 wynik powinien wyglądać następująco:
Laptop: 10 in stock
Laptop: 15 in stock
Laptop: 7 in stockA dla danych Phone, 3, 2 i 10 wynik powinien wyglądać następująco:
Phone: 3 in stock
Phone: 5 in stock
Sale failed: insufficient stockNajważniejsze jest to, że inventory.go może bezpośrednio odczytywać i modyfikować nieeksportowane pole quantity, ponieważ znajduje się w tym samym pakiecie co product.go. To przykład enkapsulacji na poziomie pakietu: współpraca wewnątrz pakietu przebiega swobodnie, a dane pozostają chronione przed innymi pakietami.
Spróbuj swoich sił
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Wczytaj nazwę produktu
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
// Wczytaj początkową ilość
initialQtyStr, _ := reader.ReadString('\n')
initialQty, _ := strconv.Atoi(strings.TrimSpace(initialQtyStr))
// Wczytaj ilość do uzupełnienia zapasów
restockStr, _ := reader.ReadString('\n')
restockAmount, _ := strconv.Atoi(strings.TrimSpace(restockStr))
// Wczytaj ilość do sprzedaży
sellStr, _ := reader.ReadString('\n')
sellAmount, _ := strconv.Atoi(strings.TrimSpace(sellStr))
// TODO: Utwórz nowy produkt za pomocą konstruktora NewProduct
// TODO: Wyświetl początkowe informacje o produkcie
// TODO: Uzupełnij zapasy produktu i wyświetl informacje
// TODO: Spróbuj sprzedać produkt i wyświetl wynik
// Jeśli sprzedaż się powiedzie, wyświetl zaktualizowane informacje
// Jeśli sprzedaż się nie powiedzie, wyświetl "Sale failed: insufficient stock"
fmt.Println() // Zastąp rzeczywistym wynikiem
}
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