Nascondimento delle informazioni in Go
Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 50 di 107.
L'occultamento delle informazioni va oltre il semplice rendere i campi non esportati. Si tratta di progettare i tuoi tipi in modo che il codice esterno conosca solo ciò che deve sapere. Nient'altro.
L'obiettivo è esporre il comportamento tramite i metodi, mantenendo completamente nascosta la struttura interna. Considera questo approccio:
type Counter struct {
value int
maxLimit int
}
func NewCounter(limit int) *Counter {
return &Counter{maxLimit: limit}
}
func (c *Counter) Increment() bool {
if c.value >= c.maxLimit {
return false
}
c.value++
return true
}
func (c *Counter) Value() int {
return c.value
}
Il codice esterno non sa che Counter usa internamente un int. Potrebbe essere un float64, una slice o qualcos'altro. L'implementazione può cambiare senza influire sul codice che usa Counter, purché i metodi si comportino allo stesso modo.
Questo principio si applica anche alle funzioni di supporto. Mantieni la logica interna non esportata:
// Esportato - parte dell'API pubblica
func (c *Counter) Reset() {
c.value = 0
}
// non esportato - funzione ausiliaria interna
func (c *Counter) isAtLimit() bool {
return c.value >= c.maxLimit
}
Nascondendo isAtLimit(), sei libero di modificarla o rimuoverla in seguito. L'incapsulamento delle informazioni crea un confine chiaro tra ciò che il tuo pacchetto promette agli utenti esterni e il suo funzionamento interno.
Sfida
FacileCostruiamo un sistema di portafoglio sicuro che dimostri l'incapsulamento delle informazioni. Creerai un portafoglio digitale in cui il saldo interno e la cronologia delle transazioni sono completamente nascosti al codice esterno: viene esposto solo il comportamento tramite metodi.
Organizzerai il codice in due file:
wallet.go: crea una structWalletche nasconde tutto il suo stato interno. Il portafoglio dovrebbe tenere traccia internamente del saldo attuale e di un limite di spesa, ma il codice esterno non dovrebbe mai sapere come questi dati sono memorizzati o rappresentati. Esponi il comportamento tramite questi metodi:NewWallet(limit float64) *Wallet- costruttore che crea un portafoglio con il limite di spesa indicato e saldo pari a zeroDeposit(amount float64) bool- aggiunge denaro al portafoglio, restituiscetruese l'operazione riesce (l'importo deve essere positivo)Spend(amount float64) bool- preleva denaro se il saldo è sufficiente E l'importo non supera il limite di spesa, restituiscetruese l'operazione riesceBalance() float64- restituisce il saldo attualeStatus() string- restituisce un messaggio di stato nel formato:Balance: $[balance] (Limit: $[limit])
main.go: leggi dalla casella di input la configurazione del portafoglio e le transazioni, crea un portafoglio, esegui le operazioni e mostra i risultati dopo ogni azione.
Verranno forniti i seguenti input:
- Riga 1: limite di spesa
- Riga 2: importo del deposito
- Riga 3: importo della prima spesa
- Riga 4: importo della seconda spesa
Dopo aver creato il portafoglio, stampa il suo stato iniziale. Poi esegui ciascuna operazione e stampa lo stato aggiornato (se riesce) oppure Transaction failed (se l'operazione viene rifiutata). Formatta tutti gli importi in dollari con due cifre decimali.
Per esempio, dati 50, 100, 30 e 80, l'output dovrebbe essere:
Balance: $0.00 (Limit: $50.00)
Balance: $100.00 (Limit: $50.00)
Balance: $70.00 (Limit: $50.00)
Transaction failedL'ultima transazione non riesce perché $80 supera il limite di spesa di $50, anche se il saldo è sufficiente. Il portafoglio applica internamente le proprie regole senza esporre come prende queste decisioni. Il codice esterno si limita a chiamare i metodi e a ricevere i risultati, senza conoscere i dettagli interni dell'implementazione.
Provalo tu
package main
import (
"fmt"
)
func main() {
// Leggi i dati di input
var limit float64
var depositAmount float64
var spend1 float64
var spend2 float64
fmt.Scanln(&limit)
fmt.Scanln(&depositAmount)
fmt.Scanln(&spend1)
fmt.Scanln(&spend2)
// TODO: Crea un nuovo portafoglio con il limite di spesa indicato
// TODO: Stampa lo stato iniziale
// TODO: Effettua il deposito e stampa il risultato (stato o "Transaction failed")
// TODO: Effettua la prima spesa e stampa il risultato
// TODO: Effettua la seconda spesa e stampa il risultato
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti di OOP in Go
File esterniWorkspace e moduli GoPacchetti e importazioniNomi esportati e non esportatiIntroduzione all’OOP in GoGli struct come classiDefinire metodi sugli structReceiver con puntatore vs receiver con valoreInizializzazione degli structFunzioni costruttriciRiepilogo - Calcolatrice semplice4Interfacce
Introduzione alle interfacceImplementazione implicitaL’interfaccia come contrattoInterfaccia vuota (any)Asserzione di tipoSwitch sul tipoComposizione di interfacceInterfacce Stringer ed ErrorRipasso - Calcolatore di forme7Incapsulamento
Campi esportati e non esportatiIncapsulamento a livello di packageMetodi Getter e SetterNascondimento delle informazioni in GoRiepilogo - Registri degli studenti10Tipi generici (Go 1.18+)
Introduzione ai tipi genericiParametri di tipoVincoli di tipoStruct genericheSoluzione alternativa per i metodi genericiRiepilogo - Collezione generica13Modelli di progettazione Parte 1
Introduzione ai modelli di progettazioneModello SingletonModello FactoryModello Abstract FactoryModello ObserverModello Strategy2Approfondimento su tipi e struct
Tipi di base e compostiDefinizioni di tipi personalizzatiTag delle structStruct anonimeStruct annidateValori zero e predefinitiRipasso - Rubrica5Composizione invece dell'ereditarietà
Perché Go non ha l'ereditarietàFondamenti dell'embedding delle structPromozione dei metodiEmbedding di più structEmbedding e aggregazione a confrontoMascheramento dei metodi embeddedRiepilogo: gerarchia dei dipendenti8Gestione degli errori e OOP
L'interfaccia errorTipi di errore personalizzatiWrapping degli errori (fmt.Errorf)Errori sentinellaerrors.Is() ed errors.As()Panic, Defer e RecoverRiepilogo - Parser di file11Libreria standard e OOP
io.Reader e io.Writersort.InterfaceInterfaccia fmt.Stringerencoding/json con le structInterfaccia http.HandlerRipasso - modelli REST API14Modelli di progettazione – Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeMiddleware come Decorator3Puntatori e memoria
Concetti base dei puntatori in GoPuntatori a structPassaggio per valore vs per riferimentoLa funzione new()Garbage collection in GoRiepilogo - Costruttore di liste concatenate6Polimorfismo in Go
Polimorfismo tramite interfacceDuck typing in GoRegole di soddisfacimento delle interfacceCollezioni polimorficheIniezione delle dipendenzeRiepilogo - Elaboratore di pagamenti9Concorrenza e OOP
Basi delle goroutineCanali e comunicazioneCanali bufferizzati vs non bufferizzatiIstruzione selectsync.Mutex e sync.RWMutexsync.WaitGroupProgettazione di struct thread-safeRiepilogo: worker poolEsercitati da solo: Compilatore Go online