Паттерн функциональных опций
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 79 из 107.
При создании структур с множеством необязательных полей конфигурации функции-конструкторы могут стать громоздкими. Паттерн функциональных опций элегантно решает эту проблему, используя функции для настройки объектов.
Рассмотрим структуру сервера с несколькими необязательными настройками. Вместо конструктора с множеством параметров мы определяем тип option и функции, которые возвращают options:
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
}
}Конструктор принимает вариативный список options и применяет каждый из них:
func NewServer(host string, opts ...Option) *Server {
s := &Server{
host: host,
port: 8080, // по умолчанию
timeout: 30, // по умолчанию
}
for _, opt := range opts {
opt(s)
}
return s
}Теперь создание серверов становится понятным и гибким:
// Использовать значения по умолчанию
s1 := NewServer("localhost")
// Настроить конкретные параметры
s2 := NewServer("localhost", WithPort(3000), WithTimeout(60))Этот шаблон особенно эффективен, потому что вызывающий код указывает только то, что ему нужно, defaults ясно определены в одном месте, а добавление новых options не нарушает существующий код. Он широко используется в библиотеках Go, таких как grpc и zap.
Задание
ЛегкоДавайте создадим настраиваемое подключение к database с использованием шаблона функциональных опций! Этот шаблон особенно удобен, когда у вас много необязательных настроек с разумными значениями по умолчанию — именно то, что нужно подключению к database.
Вы организуете код в двух файлах:
database.go: Определите тип подключения к database и function опций.Создайте структуру
DBConnectionсо следующими неэкспортируемыми полями:host(string)port(int)username(string)password(string)maxConnections(int)timeout(int) — в секундах
Определите тип
Optionкак function, которая изменяет*DBConnection.Создайте следующие function опций, возвращающие
Option:WithPort(port int)WithCredentials(username, password string)WithMaxConnections(max int)WithTimeout(seconds int)
Создайте конструктор
NewDBConnection(host string, opts ...Option), который возвращает*DBConnection. Установите следующие значения по умолчанию: port5432, username"admin", password"secret", maxConnections10, timeout30. Примените все переданные опции после установки значений по умолчанию.Добавьте метод
ConnectionString(), который возвращает информацию о подключении в следующем формате:[username]:[password]@[host]:[port] (max:[maxConnections], timeout:[timeout]s)main.go: Создайте подключения к database с различными конфигурациями.Сначала прочитайте host, затем количество применяемых опций. Для каждой опции прочитайте её тип и значение или значения:
port, затем номер portcredentials, затем username и password (две строки)maxconn, затем количество max connectionstimeout, затем timeout в секундах
Создайте
DBConnectionс указанными опциями и выведите его строку подключения.
Будут предоставлены следующие входные данные:
- Строка 1: имя host
- Строка 2: количество опций
- Следующие строки: тип опции и её значение или значения
Например, для:
localhost
0Ваш вывод должен быть таким:
admin:secret@localhost:5432 (max:10, timeout:30s)А для:
db.example.com
2
port
3306
credentials
root
mypasswordВаш вывод должен быть таким:
root:mypassword@db.example.com:3306 (max:10, timeout:30s)А для:
production.server
4
port
5433
credentials
dbuser
securepass123
maxconn
50
timeout
60Ваш вывод должен быть таким:
dbuser:securepass123@production.server:5433 (max:50, timeout:60s)Обратите внимание: вызывающий код указывает только нужные ему опции, а для неуказанных настроек используются разумные значения по умолчанию. В этом и заключается элегантность шаблона функциональных опций!
Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Прочитать хост
host, _ := reader.ReadString('\n')
host = strings.TrimSpace(host)
// Прочитать количество опций
numOptionsStr, _ := reader.ReadString('\n')
numOptions, _ := strconv.Atoi(strings.TrimSpace(numOptionsStr))
// Собрать опции
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: Добавить опцию WithPort в opts
_ = port // Удалить эту строку при реализации
case "credentials":
username, _ := reader.ReadString('\n')
username = strings.TrimSpace(username)
password, _ := reader.ReadString('\n')
password = strings.TrimSpace(password)
// TODO: Добавить опцию WithCredentials в opts
_, _ = username, password // Удалить эту строку при реализации
case "maxconn":
maxStr, _ := reader.ReadString('\n')
maxConn, _ := strconv.Atoi(strings.TrimSpace(maxStr))
// TODO: Добавить опцию WithMaxConnections в opts
_ = maxConn // Удалить эту строку при реализации
case "timeout":
timeoutStr, _ := reader.ReadString('\n')
timeout, _ := strconv.Atoi(strings.TrimSpace(timeoutStr))
// TODO: Добавить опцию WithTimeout в opts
_ = timeout // Удалить эту строку при реализации
}
}
// TODO: Создать DBConnection с помощью NewDBConnection с host и opts
// TODO: Вывести строку подключения с помощью метода ConnectionString()
_ = opts // Удалить эту строку при реализации
fmt.Println("TODO: Print connection string here")
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker Pool12Продвинутые концепции ООП
Паттерн функциональных опцийПаттерн Builder в GoЦепочки вызовов методовПсевдонимы и определения типовОсновы рефлексииОбзор генерации кодаПотренируйтесь самостоятельно: Онлайн-компилятор Go