Comments 7
К сожалению на практике это будет выглядеть так:
Кажется, такой вариант все же более читаемый с меньшим количеством неймспейсов в коде. И фокус сразу на параметрах:
И в модуле тоже значительно меньше писанины, не нужно будет объявлять функцию на каждый параметр.
module.NewServer(
":8080",
module.WithWhitelistedIP("10.0.0.0/8"),
module.Timeout(10 * time.Second),
module.TLS(&TLSConfig{})
)
Кажется, такой вариант все же более читаемый с меньшим количеством неймспейсов в коде. И фокус сразу на параметрах:
module.NewServer(&module.ServerOptions{
Bind: ":8080",
Whitelist: module.IPRange("10.0.0.0/8"),
Timeout: 10 * time.Seconds,
TLS: &TLSConfig{}
})
И в модуле тоже значительно меньше писанины, не нужно будет объявлять функцию на каждый параметр.
Именно так оно порой и выглядит.
Возможно я делаю что-то неправильно, но обычно я совмещаю подходы со структурой конфигурации и функциональными параметрами. Часто конфиги лежат в каком нибудь yaml или json файле и их действительно удобно получать в структуру. А что-то вроде сквозных логгеров со своими настройками или конфигурацией, требующей предварительной обработки, довольно комфортно передаются функционально. Мне кажется, что это выгладит не так уж и монструозно:
Возможно я делаю что-то неправильно, но обычно я совмещаю подходы со структурой конфигурации и функциональными параметрами. Часто конфиги лежат в каком нибудь yaml или json файле и их действительно удобно получать в структуру. А что-то вроде сквозных логгеров со своими настройками или конфигурацией, требующей предварительной обработки, довольно комфортно передаются функционально. Мне кажется, что это выгладит не так уж и монструозно:
log := logger.New()
cfg, err := parseConfigFromSomewhere()
// handle error
module.NewServer(
cfg,
module.SetLogger(log),
module.TLS(&TLSConfig{}),
)
А в чем профит отдельного типа Option, если можно как-то так?
log := logger.New()
cfg, err := parseConfigFromSomewhere()
// handle error
module.NewServer(
cfg.
SetLogger(log).
SetTLS(&TLSConfig{}),
)
Если я вас правильно понял, то в вашем варианте получается, что сам конфиг выглядит примерно так:
А сервер так:
С cfg.tls все получилось хорошо, а вот с logger уже не все так просто. Его нужно либо использовать через вызов s.cfg.logger либо выносить из конфига в Server и держать сразу в двух местах. И оба варианта мне не очень нравятся. Также не очень понятно как в NewServer создавать значения по умолчанию без перебора конфига на nil значения. В остальном ваш метод выглядит приятно, спасибо за подсказку.
Config
type Config struct {
Bind string
logger *logger.Logger
tls *tls.Config
}
func (c *Config) SetLogger(log *logger.Logger) *Config {
c.logger = log
return c
}
func (c *Config) SetTLS(t *tls.Config) *Config {
c.tls = t
return c
}
А сервер так:
Server
type Server struct {
cfg Config
logger *log.Logger
http *http.Server
}
func NewServer(cfg *Config) *Server {
srv := &http.Server{
Addr: cfg.Bind,
TLSConfig: cfg.tls,
}
return &Server{
cfg: *cfg,
logger: cfg.logger,
http: srv,
}
}
С cfg.tls все получилось хорошо, а вот с logger уже не все так просто. Его нужно либо использовать через вызов s.cfg.logger либо выносить из конфига в Server и держать сразу в двух местах. И оба варианта мне не очень нравятся. Также не очень понятно как в NewServer создавать значения по умолчанию без перебора конфига на nil значения. В остальном ваш метод выглядит приятно, спасибо за подсказку.
(примечание переводчика — блог с оригинальной статьей Дейва Чейни более не существует, не найдя перепечаток я заменил ссылку на статью ссылкой на видео с его доклада)
Блог на месте. dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
Проблемы доступности ссылки из России известно из-за кого… Поэтому VPN
Спасибо за перевод. Статья полезная.
Sign up to leave a comment.
Функциональные опции на стероидах