Menu
Coddy logo textTech

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.

challenge icon

Sfida

Facile

Costruiamo 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 DBConnection con questi campi non esportati:

    • host (string)
    • port (int)
    • username (string)
    • password (string)
    • maxConnections (int)
    • timeout (int) - in secondi

    Definisci un tipo Option come 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: port 5432, username "admin", password "secret", maxConnections 10, timeout 30. 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):

    • port seguito dal numero della porta
    • credentials seguito da username e password (due righe)
    • maxconn seguito dal numero massimo di connessioni
    • timeout seguito dal timeout in secondi

    Crea un DBConnection con 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
0

Il tuo output dovrebbe essere:

admin:secret@localhost:5432 (max:10, timeout:30s)

E dato:

db.example.com
2
port
3306
credentials
root
mypassword

Il 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
60

Il 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")
}
quiz iconMettiti alla prova

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

Esercitati da solo: Compilatore Go online