On the contrary, exposing the options struct to the user you’ll get something like this:
Your package:
type Options struct {
FooLogger *log.Logger
BarLogger *log.Logger
}
var defaultOptions = &Options{
FooLogger: globalLogger,
BarLogger: globalLogger,
}
type foo struct {
logger *log.Logger
}
type Bar struct {
foo *foo
data int
logger *log.Logger
}
func NewBar(data int, opts *Options) *Bar {
if opts == nil {
return &Bar{
foo: &foo{logger: defaultOptions.FooLogger},
data: data,
logger: defaultOptions.BarLogger,
}
// But here you will have to check values for every option which is defined in `Options` struct
// and use default if it was not set.
if opts.FooLogger != nil {
defaultOptions.FooLogger = opts.FooLogger
}
if opts.BarLogger != nil {
defaultOptions.BarLogger = opts.BarLogger
}
return &Bar{
foo: &foo{logger: defaultOptions.FooLogger},
data: data,
logger: defaultOptions.BarLogger,
}
}
- It looks good with pointers, since you can check them on just nil, but some over parameters will be a headache to track.
And in my code, instead of simply passing functions into constructor I’ll have to do something like:
opts := &Options{
// Setup what I want.
}
b := NewBar(data, opts)
// Or without options.
b := NewBar(data, nil)
All in all, both ways can take place. It’s just different patterns and you can use what ever fits your goal more.
IMHO, functions look as more flexible and cleaner approach.