Pattern delle opzioni funzionali
Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 79 di 107.
Quando si creano struct con molti campi di configurazione facoltativi, le funzioni costruttrici possono diventare difficili da gestire. Il Functional Options Pattern risolve elegantemente il problema usando funzioni per configurare gli oggetti.
Considera una struct server con più impostazioni opzionali. Invece di un costruttore con molti parametri, definiamo un tipo di opzione e funzioni che restituiscono opzioni:
type Server struct {
host string
port int
timeout int
}
type Option func(*Server)
func WithPort(port int) Option {
return func(s *Server) {
s.port = port
}
}
func WithTimeout(timeout int) Option {
return func(s *Server) {
s.timeout = timeout
}
}Il costruttore accetta un elenco variadico di opzioni e applica ciascuna di esse:
func NewServer(host string, opts ...Option) *Server {
s := &Server{
host: host,
port: 8080, // predefinito
timeout: 30, // predefinito
}
for _, opt := range opts {
opt(s)
}
return s
}Ora creare server diventa leggibile e flessibile:
// Usa i valori predefiniti
s1 := NewServer("localhost")
// Personalizza opzioni specifiche
s2 := NewServer("localhost", WithPort(3000), WithTimeout(60))Questo schema è efficace perché chi chiama specifica solo ciò di cui ha bisogno, i valori predefiniti sono chiari e raccolti in un unico posto e l’aggiunta di nuove opzioni non interrompe il codice esistente. È ampiamente utilizzato nelle librerie Go come grpc e zap.
Sfida
FacileCostruiamo una connessione al database configurabile usando il Functional Options Pattern! Questo pattern è particolarmente utile quando hai molte impostazioni opzionali con valori predefiniti sensati, proprio ciò di cui ha bisogno una connessione al database.
Organizzerai il codice in due file:
database.go: definisci il tipo di connessione al database e le funzioni di opzione.Crea una struct
DBConnectioncon questi campi non esportati:host(string)port(int)username(string)password(string)maxConnections(int)timeout(int) - in secondi
Definisci un tipo
Optioncome funzione che modifica un*DBConnection.Crea queste funzioni di opzione che restituiscono un
Option:WithPort(port int)WithCredentials(username, password string)WithMaxConnections(max int)WithTimeout(seconds int)
Crea un costruttore
NewDBConnection(host string, opts ...Option)che restituisce un*DBConnection. Imposta questi valori predefiniti: port5432, username"admin", password"secret", maxConnections10, timeout30. Applica tutte le opzioni fornite dopo aver impostato i valori predefiniti.Aggiungi un metodo
ConnectionString()che restituisce le informazioni sulla connessione in questo formato:[username]:[password]@[host]:[port] (max:[maxConnections], timeout:[timeout]s)main.go: crea connessioni al database con configurazioni diverse.Leggi l'host, poi leggi il numero di opzioni da applicare. Per ogni opzione, leggi il tipo di opzione e il relativo valore (o i relativi valori):
portseguito dal numero della portacredentialsseguito da username e password (due righe)maxconnseguito dal numero massimo di connessionitimeoutseguito dal timeout in secondi
Crea un
DBConnectioncon le opzioni specificate e stampa la stringa di connessione.
Verranno forniti i seguenti input:
- Riga 1: nome dell'host
- Riga 2: numero di opzioni
- Righe successive: tipo di opzione e relativo valore (o relativi valori)
Per esempio, dato:
localhost
0Il tuo output dovrebbe essere:
admin:secret@localhost:5432 (max:10, timeout:30s)E dato:
db.example.com
2
port
3306
credentials
root
mypasswordIl tuo output dovrebbe essere:
root:mypassword@db.example.com:3306 (max:10, timeout:30s)E dato:
production.server
4
port
5433
credentials
dbuser
securepass123
maxconn
50
timeout
60Il tuo output dovrebbe essere:
dbuser:securepass123@production.server:5433 (max:50, timeout:60s)Nota che chi chiama specifica solo le opzioni necessarie: le impostazioni non specificate usano valori predefiniti sensati. Questa è l'eleganza del Functional Options Pattern!
Provalo tu
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leggi l'host
host, _ := reader.ReadString('\n')
host = strings.TrimSpace(host)
// Leggi il numero di opzioni
numOptionsStr, _ := reader.ReadString('\n')
numOptions, _ := strconv.Atoi(strings.TrimSpace(numOptionsStr))
// Raccogli le opzioni
var opts []Option
for i := 0; i < numOptions; i++ {
optionType, _ := reader.ReadString('\n')
optionType = strings.TrimSpace(optionType)
switch optionType {
case "port":
portStr, _ := reader.ReadString('\n')
port, _ := strconv.Atoi(strings.TrimSpace(portStr))
// TODO: Aggiungi l'opzione WithPort a opts
_ = port // Rimuovi questa riga durante l'implementazione
case "credentials":
username, _ := reader.ReadString('\n')
username = strings.TrimSpace(username)
password, _ := reader.ReadString('\n')
password = strings.TrimSpace(password)
// TODO: Aggiungi l'opzione WithCredentials a opts
_, _ = username, password // Rimuovi questa riga durante l'implementazione
case "maxconn":
maxStr, _ := reader.ReadString('\n')
maxConn, _ := strconv.Atoi(strings.TrimSpace(maxStr))
// TODO: Aggiungi l'opzione WithMaxConnections a opts
_ = maxConn // Rimuovi questa riga durante l'implementazione
case "timeout":
timeoutStr, _ := reader.ReadString('\n')
timeout, _ := strconv.Atoi(strings.TrimSpace(timeoutStr))
// TODO: Aggiungi l'opzione WithTimeout a opts
_ = timeout // Rimuovi questa riga durante l'implementazione
}
}
// TODO: Crea DBConnection usando NewDBConnection con host e opts
// TODO: Stampa la stringa di connessione usando il metodo ConnectionString()
_ = opts // Rimuovi questa riga durante l'implementazione
fmt.Println("TODO: Print connection string here")
}
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 pool12Concetti avanzati di OOP
Pattern delle opzioni funzionaliPattern Builder in GoConcatenamento dei metodiAlias di tipo e definizioni a confrontoNozioni di base sulla reflectionPanoramica sulla generazione del codiceEsercitati da solo: Compilatore Go online