Привет, Хабр! Представляю вашему вниманию перевод статьи Functional options on steroids от автора Mark Sagi-Kazar.
Функциональные опции — это парадигма в Go для чистых и расширяемых API. Она популяризирована Дейвом Чейни и Робом Пайком. Этот пост о практиках, которые появились вокруг шаблона с тех пор как он был впервые представлен.
Функциональные опции появились как способ создания хороших и чистых API с конфигурацией, включающей необязательные параметры. Есть много очевидных способов сделать это (конструктор, структура конфигурации, сеттеры и т. д.), но когда нужно передавать десятки опций они плохо читаются и не дают на выходе таких хороших API, как функциональные опции.
obj := New(arg1, arg2)
// I can still do this...
obj := New(arg1, arg2)
// ...but this works too
obj := New(arg1, arg2, myOption1, myOption2)
type Server struct {
addr string
}
// NewServer initializes a new Server listening on addr.
func NewServer(addr string) *Server {
return &Server {
addr: addr,
}
}
type Server struct {
addr string
// default: no timeout
timeout time.Duration
}
// Timeout configures a maximum length of idle connection in Server.
func Timeout(timeout time.Duration) func(*Server) {
return func(s *Server) {
s.timeout = timeout
}
}
// NewServer initializes a new Server listening on addr with optional configuration.
func NewServer(addr string, opts ...func(*Server)) *Server {
server := &Server {
addr: addr,
}
// apply the list of options to Server
for _, opt := range opts {
opt(server)
}
return server
}
// no optional paramters, use defaults
server := NewServer(":8080")
// configure a timeout in addition to the address
server := NewServer(":8080", Timeout(10 * time.Second))
// configure a timeout and TLS in addition to the address
server := NewServer(":8080", Timeout(10 * time.Second), TLS(&TLSConfig{}))
// constructor variants
server := NewServer(":8080")
server := NewServerWithTimeout(":8080", 10 * time.Second)
server := NewServerWithTimeoutAndTLS(":8080", 10 * time.Second, &TLSConfig{})
// config struct
server := NewServer(":8080", Config{})
server := NewServer(":8080", Config{ Timeout: 10 * time.Second })
server := NewServer(":8080", Config{ Timeout: 10 * time.Second, TLS: &TLSConfig{} })
// Option configures a Server.
type Option func(s *Server)
func Timeout(timeout time.Duration) func(*Server) { /*...*/ }
// reads: a new server accepts an address
// and a set of functions that accepts the server itself
func NewServer(addr string, opts ...func(s *Server)) *Server
// VS
func Timeout(timeout time.Duration) Option { /*...*/ }
// reads: a new server accepts an address and a set of options
func NewServer(addr string, opts ...Option) *Server
defaultOptions := []Option{Timeout(5 * time.Second)}
server1 := NewServer(":8080", append(defaultOptions, MaxConnections(10))...)
server2 := NewServer(":8080", append(defaultOptions, RateLimit(10, time.Minute))...)
server3 := NewServer(":8080", append(defaultOptions, Timeout(10 * time.Second))...)
// Options turns a list of Option instances into an Option.
func Options(opts ...Option) Option {
return func(s *Server) {
for _, opt := range opts {
opt(s)
}
}
}
defaultOptions := Options(Timeout(5 * time.Second))
server1 := NewServer(":8080", defaultOptions, MaxConnections(10))
server2 := NewServer(":8080", defaultOptions, RateLimit(10, time.Minute))
server3 := NewServer(":8080", defaultOptions, Timeout(10 * time.Second))
type Logger interface {
Info(msg string)
Error(msg string)
}
func WithLogger(logger Logger) Option {
return func(s *Server) {
s.logger = logger
}
}
// reads: create a new server that listens on :8080 with a logger
NewServer(":8080", WithLogger(logger))
type Server struct {
// ...
whitelistIPs []string
}
func WithWhitelistedIP(ip string) Option {
return func(s *Server) {
s.whitelistIPs = append(s.whitelistIPs, ip)
}
}
NewServer(":8080", WithWhitelistedIP("10.0.0.0/8"), WithWhitelistedIP("172.16.0.0/12"))
func SetWhitelistedIP(ip string) Option {
return func(s *Server) {
s.whitelistIPs = []string{ip}
}
}
NewServer(
":8080",
WithWhitelistedIP("10.0.0.0/8"),
WithWhitelistedIP("172.16.0.0/12"),
SetWhitelistedIP("192.168.0.0/16"), // overwrites any previous values
)
// PublicPreset configures a Server for public usage.
func PublicPreset() Option {
return Options(
WithTimeout(10 * time.Second),
MaxConnections(10),
)
}
// InternalPreset configures a Server for internal usage.
func InternalPreset() Option {
return Options(
WithTimeout(20 * time.Second),
WithWhitelistedIP("10.0.0.0/8"),
)
}
func NewServer(addr string, opts ...func(*Server)) *Server {
server := &Server {
addr: addr,
logger: noopLogger{},
}
// apply the list of options to Server
for _, opt := range opts {
opt(server)
}
return server
}
func NewServer(addr string, opts ...func(*Server)) *Server {
server := &Server {
addr: addr,
}
// what are the defaults?
opts = append([]Option{DefaultPreset()}, opts...)
// apply the list of options to Server
for _, opt := range opts {
opt(server)
}
return server
}
type Config struct {
Timeout time.Duration
}
type Option func(c *Config)
type Server struct {
// ...
config Config
}
config := Config{
Timeout: 10 * time.Second
// ...
// lots of other options
}
NewServer(":8080", WithConfig(config))
config := Config{
Timeout: 10 * time.Second
// ...
// lots of other options
}
NewServer(":8080", WithConfig(config), WithTimeout(20 * time.Second))
type Timeout time.Duration
NewServer(":8080", Timeout(time.Minute))
// Option configures a Server.
type Option interface {
// apply is unexported,
// so only the current package can implement this interface.
apply(s *Server)
}
// Timeout configures a maximum length of idle connection in Server.
type Timeout time.Duration
func (t Timeout) apply(s *Server) {
s.timeout = time.Duration(t)
}
// Options turns a list of Option instances into an Option.
type Options []Option
func (o Options) apply(s *Server) {
for _, opt := range o {
o.apply(s)
}
}
type Config struct {
Timeout time.Duration
}
func (c Config) apply(s *Server) {
s.config = c
}
// ServerOption configures a Server.
type ServerOption interface {
applyServer(s *Server)
}
// ClientOption configures a Client.
type ClientOption interface {
applyClient(c *Client)
}
// Option configures a Server or a Client.
type Option interface {
ServerOption
ClientOption
}
func WithLogger(logger Logger) Option {
return withLogger{logger}
}
type withLogger struct {
logger Logger
}
func (o withLogger) applyServer(s *Server) {
s.logger = o.logger
}
func (o withLogger) applyClient(c *Client) {
c.logger = o.logger
}
NewServer(":8080", WithLogger(logger))
NewClient("http://localhost:8080", WithLogger(logger))
К сожалению, не доступен сервер mySQL