Functional Options

Functional options is a pattern in which you declare an opaque Option typethat records information in some internal struct. You accept a variadic numberof these options and act upon the full information recorded by the options onthe internal struct.

Use this pattern for optional arguments in constructors and other public APIsthat you foresee needing to expand, especially if you already have three ormore arguments on those functions.

BadGood
  1. // package db
  2.  
  3. func Connect(
  4. addr string,
  5. timeout time.Duration,
  6. caching bool,
  7. ) (Connection, error) {
  8. // …
  9. }
  10. // Timeout and caching must always be provided,
  11. // even if the user wants to use the default.
  12. db.Connect(addr, db.DefaultTimeout, db.DefaultCaching)
  13. db.Connect(addr, newTimeout, db.DefaultCaching)
  14. db.Connect(addr, db.DefaultTimeout, false / caching /)
  15. db.Connect(addr, newTimeout, false / caching /)
  1. type options struct {
  2. timeout time.Duration
  3. caching bool
  4. }
  5.  
  6. // Option overrides behavior of Connect.
  7. type Option interface {
  8. apply(options)
  9. }
  10.  
  11. type optionFunc func(options)
  12. func (f optionFunc) apply(o options) {
  13. f(o)
  14. }
  15.  
  16. func WithTimeout(t time.Duration) Option {
  17. return optionFunc(func(o options) {
  18. o.timeout = t
  19. })
  20. }
  21. func WithCaching(cache bool) Option {
  22. return optionFunc(func(o options) {
  23. o.caching = cache
  24. })
  25. }
  26.  
  27. // Connect creates a connection.
  28. func Connect(
  29. addr string,
  30. opts Option,
  31. ) (*Connection, error) {
  32. options := options{
  33. timeout: defaultTimeout,
  34. caching: defaultCaching,
  35. }
  36.  
  37. for _, o := range opts {
  38. o.apply(&options)
  39. }
  40.  
  41. // …
  42. }
  43.  
  44. // Options must be provided only if needed.
  45.  
  46. db.Connect(addr)
  47. db.Connect(addr, db.WithTimeout(newTimeout))
  48. db.Connect(addr, db.WithCaching(false))
  49. db.Connect(
  50. addr,
  51. db.WithCaching(false),
  52. db.WithTimeout(newTimeout),
  53. )

See also,