Encapsulation au niveau du package
Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy — leçon 48 sur 107.
Alors que la leçon précédente portait sur la visibilité au niveau des champs, l'encapsulation de Go fonctionne en réalité au niveau du package. Tout ce qui se trouve au sein du même package — quel que soit le fichier dans lequel il se trouve — peut accéder aux champs et fonctions non exportés.
Considérez un package avec plusieurs fichiers :
// account/account.go
package account
type Account struct {
balance int // non exporté
}
func (a *Account) Deposit(amount int) {
a.balance += amount
}
// account/helper.go
package account
func ResetAccount(a *Account) {
a.balance = 0 // fonctionne - même package
}
Les deux fichiers appartiennent au package account, donc helper.go peut accéder directement au champ balance non exporté. C'est intentionnel — cela vous permet de diviser la logique du package sur plusieurs fichiers tout en conservant l'accès interne.
Cependant, le code en dehors du paquet ne peut pas accéder aux membres non exportés :
// main.go
package main
import "account"
func main() {
acc := &account.Account{}
acc.Deposit(100) // fonctionne - méthode exportée
// acc.balance = 50 // erreur - champ non exporté
// account.ResetAccount // fonctionnerait si ResetAccount était exporté
}
Cette frontière au niveau du paquet est le mécanisme d'encapsulation principal de Go. Vous concevez les paquets comme des unités cohérentes où le code interne collabore librement, tandis que le code externe interagit uniquement via l'API exportée. Considérez chaque paquet comme un module autonome doté d'une interface publique claire.
Défi
FacileConstruisons un système de gestion d'inventaire qui démontre comment plusieurs fichiers au sein d'un même package peuvent collaborer en accédant directement à des champs non exportés, tout en gardant ces données cachées du code externe.
Vous allez créer trois fichiers qui travaillent ensemble dans le package main :
product.go: Définissez une structureProductavec un champ exportéNameet un champ non exportéquantity(int). Ajoutez une méthode exportéeInfo() stringqui retourne le format :[Name]: [quantity] in stockinventory.go: Créez des fonctions d'aide qui travaillent avec les produits en accédant directement au champ non exportéquantity(possible car ils sont dans le même package). Implémentez :Restock(p *Product, amount int)- ajoute le montant à la quantité du produitSell(p *Product, amount int) bool- soustrait le montant de la quantité si le stock est suffisant, retournetrueen cas de succès oufalsesi le stock est insuffisantNewProduct(name string, initialQty int) *Product- constructeur qui crée un produit avec le nom et la quantité initiale donnés
main.go: Lisez les détails du produit depuis l'entrée, créez un produit en utilisant le constructeur, effectuez des opérations d'inventaire et affichez les résultats. Après chaque opération, affichez les informations du produit pour montrer l'état mis à jour.
Les entrées suivantes seront fournies :
- Ligne 1 : Nom du produit
- Ligne 2 : Quantité initiale (entier)
- Ligne 3 : Montant du réapprovisionnement (entier)
- Ligne 4 : Montant de la vente (entier)
Après avoir créé le produit, affichez ses informations initiales. Ensuite, réapprovisionnez et affichez à nouveau les informations. Enfin, tentez de vendre et affichez soit les informations mises à jour (en cas de succès), soit "Sale failed: insufficient stock" (si la quantité est insuffisante).
Par exemple, avec Laptop, 10, 5, et 8, votre sortie devrait être :
Laptop: 10 in stock
Laptop: 15 in stock
Laptop: 7 in stockEt avec Phone, 3, 2, et 10, votre sortie devrait être :
Phone: 3 in stock
Phone: 5 in stock
Sale failed: insufficient stockL'idée clé ici est que inventory.go peut lire et modifier directement le champ non exporté quantity car il se trouve dans le même package que product.go. C'est l'encapsulation au niveau du package en action : la collaboration interne se fait librement tandis que les données restent protégées des packages extérieurs.
Aide-mémoire
L'encapsulation en Go fonctionne au niveau du paquet (package), et non au niveau du fichier. Tous les fichiers au sein d'un même paquet peuvent accéder aux champs et fonctions non exportés, quel que soit le fichier dans lequel ils sont définis.
Accès au niveau du paquet
Les membres non exportés sont accessibles dans tous les fichiers du même paquet :
// account/account.go
package account
type Account struct {
balance int // unexported
}
func (a *Account) Deposit(amount int) {
a.balance += amount
}
// account/helper.go
package account
func ResetAccount(a *Account) {
a.balance = 0 // works - same package
}
Restrictions des paquets externes
Le code situé en dehors du paquet ne peut pas accéder aux membres non exportés :
// main.go
package main
import "account"
func main() {
acc := &account.Account{}
acc.Deposit(100) // works - exported method
// acc.balance = 50 // error - unexported field
}
Cette conception vous permet de répartir la logique d'un paquet sur plusieurs fichiers tout en conservant l'accès interne, créant ainsi des unités cohérentes avec des interfaces publiques claires.
Essayez vous-même
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Lire le nom du produit
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
// Lire la quantité initiale
initialQtyStr, _ := reader.ReadString('\n')
initialQty, _ := strconv.Atoi(strings.TrimSpace(initialQtyStr))
// Lire la quantité de réapprovisionnement
restockStr, _ := reader.ReadString('\n')
restockAmount, _ := strconv.Atoi(strings.TrimSpace(restockStr))
// Lire la quantité à vendre
sellStr, _ := reader.ReadString('\n')
sellAmount, _ := strconv.Atoi(strings.TrimSpace(sellStr))
// TODO: Créer un nouveau produit en utilisant le constructeur NewProduct
// TODO: Afficher les informations initiales du produit
// TODO: Réapprovisionner le produit et afficher les informations
// TODO: Tenter de vendre et afficher le résultat
// Si la vente réussit, afficher les informations mises à jour
// Si la vente échoue, afficher "Sale failed: insufficient stock"
fmt.Println() // Remplacer par la sortie réelle
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Fondamentaux de la POO en Go
Fichiers externesEspace de travail et Modules GoPackages et ImportsNoms exportés vs non exportésIntroduction à la POO en GoLes Structs comme ClassesDéfinir des méthodes sur les StructsReceveurs par pointeur vs par valeurInitialisation de StructsFonctions constructeursRécapitulatif - Calculatrice simple4Interfaces
Introduction aux interfacesImplémentation impliciteL'interface comme contratInterface vide (any)Assertion de typeType SwitchComposition d'interfacesInterfaces Stringer & ErrorRécapitulatif - Calculateur de formes7Encapsulation
Champs exportés vs non exportésEncapsulation au niveau du packageMéthodes Getter & SetterMasquage d'informations en GoRécapitulatif - Dossiers étudiants10Génériques (Go 1.18+)
Introduction aux génériquesParamètres de typeContraintes de typeStructures génériquesContournement pour les méthodes génériquesRécapitulatif - Collections génériques13Patrons de conception, Partie 1
Introduction aux Design PatternsPatron SingletonPatron FactoryPatron Abstract FactoryPatron ObserverPatron Strategy2Exploration approfondie des Types et Structs
Types de base et compositesDéfinitions de types personnalisésTags de StructStructs anonymesStructs imbriquéesValeurs par défaut et Zero ValuesRécapitulatif - Carnet de contacts5Composition plutôt qu'héritage
Pourquoi Go n'a pas d'héritageLes bases de l'imbrication de structsPromotion de méthodesImbriquer plusieurs structsImbrication vs AgrégationMasquage de méthodes imbriquéesRécapitulatif - Hiérarchie des employés8Gestion des erreurs & POO
L'interface errorTypes d'erreurs personnalisésError Wrapping (fmt.Errorf)Erreurs sentinelleserrors.Is() et errors.As()Panic, Defer et RecoverRécapitulatif - File Parser11Bibliothèque standard et POO
io.Reader & io.WriterL'interface sort.InterfaceL'interface fmt.Stringerencoding/json avec les StructsL'interface http.HandlerRécapitulatif - Modèles d'API REST14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeMiddleware en tant que Décorateur3Pointeurs et Mémoire
Bases des pointeurs en GoPointeurs vers des structsPassage par valeur vs référenceLa fonction new()Garbage Collection en GoRécapitulatif - Constructeur de liste chaînée6Le polymorphisme en Go
Le polymorphisme via les interfacesLe Duck Typing en GoRègles de satisfaction des interfacesCollections polymorphesInjection de dépendancesRécapitulatif - Processeur de paiement9Concurrence et POO
Bases des GoroutinesChannels et CommunicationChannels avec buffer vs sans bufferL'instruction Selectsync.Mutex et sync.RWMutexsync.WaitGroupConception de structures Thread-SafeRécapitulatif - Worker Pool