Zig часто використовує підхід який називають “Options bag pattern” (якщо його взагалі так хтось називає). Останнім часом я почав використовувати його в Go.
Go структура з аргументами
package fediverse
type NewClientArgs struct{
HTTPClient *http.Client
UseV2 *bool
FluentOpts []fluent.Opts
}
func (nca NewClientArgs) WithDefaults() NewClientArgs {
if nca.HTTPClient == nil {
nca.HTTPClient = http.DefaultClient
}
if nca.UseV2 == nil{
nca.UseV2 = new(viper.GetBool("some_env_var"))
}
if *nca.UseV2{
nca.FluentOpts = append(nca.FluentOpts, fluent.WithSomethingCool, fluent.WithNewGen)
}
return nca
}
type Client struct{}
func NewClient(url *url.URL, args NewClientArgs) *Client {
args = args.WithDefaults()
return &Client{
// set fields
// client: args.HTTPClient, // etc.
}
}
Чому мені це більше подобається? Усі опції в одному місці, їх легко додати як поле для логування, опції можуть залежати одна від одної бо порядковість ініціалізації контролюється імплементацією.
Думаю що назва NewClientOpts може бути більш вдалим у даному випадку, коли всі поля структури є опціональними, я ще не вирішив як краще називати цю структуру в коді. Також є випадки коли обов’язкові поля додаються в таку структуру, тоді назва ~Args краща.
Мутуючі функції
У Go мені здається переважає патерн коли опція це інтерфейс або функція, що приймає об’єкт для налаштування, і модифікує його.
type WithOpt func(*privateStruct)
func WithInsanity(level int) WithOpt
func WithPrice(price uint) WithOpt
type privateStruct struct{
// state
}
func New(opts ...WithOpt) *privateStruct {
v := &privateStruct{}
for _, opt := range opts {
opt(v)
}
return v
}
Мені цей підхід не подобається. Ми втратили контроль поряку застосування опцій, спосіб легко логувати параметри й потенційно ці функції можуть бути в різних файлах.
Валідація
У випадку обов’язкових полів можливо додати метод Validate.
type Args struct{
IsWater *bool
IsLiquid *bool
CanCatchFire *bool
}
func (a Args) Validate() error {
var errs []error
if a.IsWater != nil && a.CanCatchFire != nil{
errs = append(errs, errors.New("water is not compatible with fire"))
}
return errors.Join(errs...)
}