Wzorzec opcji funkcyjnych
Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 79 z 107.
Podczas tworzenia struktur z wieloma opcjonalnymi polami konfiguracji funkcje konstruktora mogą stać się nieporęczne. Wzorzec opcji funkcyjnych elegancko rozwiązuje ten problem, wykorzystując funkcje do konfigurowania obiektów.
Rozważ strukturę serwera z wieloma opcjonalnymi ustawieniami. Zamiast konstruktora z wieloma parametrami definiujemy typ opcji i funkcje, które zwracają opcje:
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
}
}Konstruktor przyjmuje listę opcji o zmiennej długości i stosuje każdą z nich:
func NewServer(host string, opts ...Option) *Server {
s := &Server{
host: host,
port: 8080, // domyślna wartość
timeout: 30, // domyślna wartość
}
for _, opt := range opts {
opt(s)
}
return s
}Teraz tworzenie serwerów staje się czytelne i elastyczne:
// Użyj wartości domyślnych
s1 := NewServer("localhost")
// Dostosuj wybrane opcje
s2 := NewServer("localhost", WithPort(3000), WithTimeout(60))Ten wzorzec sprawdza się doskonale, ponieważ wywołujący określają tylko to, czego potrzebują, wartości domyślne są jasno zdefiniowane w jednym miejscu, a dodawanie nowych opcji nie powoduje błędów w istniejącym kodzie. Jest powszechnie stosowany w bibliotekach Go, takich jak grpc i zap.
Wyzwanie
ŁatwyZbudujmy konfigurowalne połączenie z bazą danych przy użyciu wzorca opcji funkcyjnych! Ten wzorzec sprawdza się doskonale, gdy masz wiele opcjonalnych ustawień z rozsądnymi wartościami domyślnymi — dokładnie tego potrzebuje połączenie z bazą danych.
Rozmieścisz kod w dwóch plikach:
database.go: Zdefiniuj typ połączenia z bazą danych oraz funkcje opcji.Utwórz strukturę
DBConnectionz następującymi nieeksportowanymi polami:host(string)port(int)username(string)password(string)maxConnections(int)timeout(int) - w sekundach
Zdefiniuj typ
Optionjako funkcję modyfikującą*DBConnection.Utwórz następujące funkcje opcji, które zwracają wartość typu
Option:WithPort(port int)WithCredentials(username, password string)WithMaxConnections(max int)WithTimeout(seconds int)
Utwórz konstruktor
NewDBConnection(host string, opts ...Option), który zwraca*DBConnection. Ustaw następujące wartości domyślne: port5432, username"admin", password"secret", maxConnections10, timeout30. Zastosuj wszystkie podane opcje po ustawieniu wartości domyślnych.Dodaj metodę
ConnectionString(), która zwraca informacje o połączeniu w następującym formacie:[username]:[password]@[host]:[port] (max:[maxConnections], timeout:[timeout]s)main.go: Twórz połączenia z bazą danych z różnymi konfiguracjami.Wczytaj hosta, a następnie wczytaj liczbę opcji do zastosowania. Dla każdej opcji wczytaj jej typ i wartość lub wartości:
port, a następnie numer portucredentials, a następnie nazwę użytkownika i hasło (dwa wiersze)maxconn, a następnie maksymalną liczbę połączeńtimeout, a następnie limit czasu w sekundach
Utwórz
DBConnectionz określonymi opcjami i wyświetl jego ciąg połączenia.
Podane zostaną następujące dane wejściowe:
- Wiersz 1: Nazwa hosta
- Wiersz 2: Liczba opcji
- Kolejne wiersze: Typ opcji i wartość lub wartości
Na przykład dla danych:
localhost
0Oczekiwany wynik:
admin:secret@localhost:5432 (max:10, timeout:30s)A dla danych:
db.example.com
2
port
3306
credentials
root
mypasswordOczekiwany wynik:
root:mypassword@db.example.com:3306 (max:10, timeout:30s)A dla danych:
production.server
4
port
5433
credentials
dbuser
securepass123
maxconn
50
timeout
60Oczekiwany wynik:
dbuser:securepass123@production.server:5433 (max:50, timeout:60s)Zwróć uwagę, że wywołujący podają tylko potrzebne opcje: nieokreślone ustawienia przyjmują rozsądne wartości domyślne. Na tym polega elegancja wzorca opcji funkcyjnych!
Spróbuj swoich sił
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Odczytaj host
host, _ := reader.ReadString('\n')
host = strings.TrimSpace(host)
// Odczytaj liczbę opcji
numOptionsStr, _ := reader.ReadString('\n')
numOptions, _ := strconv.Atoi(strings.TrimSpace(numOptionsStr))
// Zbierz opcje
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: Dodaj opcję WithPort do opts
_ = port // Usuń tę linię podczas implementacji
case "credentials":
username, _ := reader.ReadString('\n')
username = strings.TrimSpace(username)
password, _ := reader.ReadString('\n')
password = strings.TrimSpace(password)
// TODO: Dodaj opcję WithCredentials do opts
_, _ = username, password // Usuń tę linię podczas implementacji
case "maxconn":
maxStr, _ := reader.ReadString('\n')
maxConn, _ := strconv.Atoi(strings.TrimSpace(maxStr))
// TODO: Dodaj opcję WithMaxConnections do opts
_ = maxConn // Usuń tę linię podczas implementacji
case "timeout":
timeoutStr, _ := reader.ReadString('\n')
timeout, _ := strconv.Atoi(strings.TrimSpace(timeoutStr))
// TODO: Dodaj opcję WithTimeout do opts
_ = timeout // Usuń tę linię podczas implementacji
}
}
// TODO: Utwórz DBConnection za pomocą NewDBConnection, używając host i opts
// TODO: Wypisz ciąg połączenia za pomocą metody ConnectionString()
_ = opts // Usuń tę linię podczas implementacji
fmt.Println("TODO: Print connection string here")
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy OOP w Go
Pliki zewnętrzneObszar roboczy i moduły GoPakiety i importyNazwy eksportowane i nieeksportowaneWprowadzenie do OOP w GoStruktury jako klasyDefiniowanie metod dla strukturOdbiorniki wskaźnikowe i wartościoweInicjalizacja strukturFunkcje konstruktoraPodsumowanie – prosty kalkulator4Interfejsy
Wprowadzenie do interfejsówImplementacja niejawnaInterfejs jako kontraktPusty interfejs (any)Asercja typuPrzełącznik typówKompozycja interfejsówInterfejsy Stringer i ErrorPowtórka — kalkulator figur7Hermetyzacja
Pola eksportowane i nieeksportowaneHermetyzacja na poziomie pakietuMetody pobierające i ustawiająceUkrywanie informacji w GoPowtórka – dane studentów10Typy generyczne (Go 1.18+)
Wprowadzenie do typów generycznychParametry typówOgraniczenia typówStruktury generyczneObejście ograniczeń metod generycznychPodsumowanie — generyczna kolekcja2Typy i struktury w praktyce
Typy proste i złożoneDefiniowanie własnych typówTagi pól strukturStruktury anonimoweStruktury zagnieżdżoneWartości zerowe i domyślnePodsumowanie – książka kontaktów5Kompozycja zamiast dziedziczenia
Dlaczego Go nie ma dziedziczeniaPodstawy osadzania strukturPromowanie metodOsadzanie wielu strukturOsadzanie a agregacjaPrzesłanianie osadzonych metodPodsumowanie — hierarchia pracowników8Obsługa błędów i programowanie obiektowe
Interfejs błęduWłasne typy błędówOpakowywanie błędów (fmt.Errorf)Błędy wartowniczeerrors.Is() i errors.As()Panic, Defer i RecoverPodsumowanie — parser plików3Wskaźniki i pamięć
Podstawy wskaźników w GoWskaźniki do strukturPrzekazywanie przez wartość a przez referencjęFunkcja new()Odśmiecanie pamięci w GoPowtórka — kreator listy wiązanej6Polimorfizm w Go
Polimorfizm za pomocą interfejsówDuck typing w GoZasady spełniania interfejsówKolekcje polimorficzneWstrzykiwanie zależnościPowtórka — procesor płatności9Współbieżność i OOP
Podstawy goroutinesKanały i komunikacjaKanały buforowane i niebuforowaneInstrukcja selectsync.Mutex i sync.RWMutexsync.WaitGroupProjektowanie struktur bezpiecznych wątkowoPowtórka – pula workerów12Zaawansowane zagadnienia programowania obiektowego
Wzorzec opcji funkcyjnychWzorzec Builder w GoŁańcuchowe wywoływanie metodAliasy typów a definicje typówPodstawy refleksjiPrzegląd generowania koduPoćwicz samodzielnie: Kompilator Go online