Menu
Coddy logo textTech

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.

challenge icon

Wyzwanie

Łatwy

Zbudujmy 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ę Product z eksportowanym polem Name i nieeksportowanym polem quantity (int). Dodaj eksportowaną metodę Info() string, która zwraca tekst w formacie: [Name]: [quantity] in stock
  • inventory.go: Utwórz funkcje pomocnicze, które działają na produktach, uzyskując bezpośredni dostęp do nieeksportowanego pola quantity (jest to możliwe, ponieważ znajdują się w tym samym pakiecie). Zaimplementuj:
    • Restock(p *Product, amount int) — dodaje podaną ilość do zapasu produktu
    • Sell(p *Product, amount int) bool — odejmuje podaną ilość od zapasu, jeśli jest go wystarczająco dużo; zwraca true, jeśli operacja się powiedzie, albo false, jeśli zapas jest niewystarczający
    • NewProduct(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 stock

A 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 stock

Najważ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
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Go online