136 lines
3.1 KiB
Markdown
136 lines
3.1 KiB
Markdown
# option
|
|
|
|
Base object for what I call the "Optional Parameters Pattern".
|
|
|
|
The beauty of this pattern is that you can achieve a method that can
|
|
take the following simple calling style
|
|
|
|
```
|
|
obj.Method(mandatory1, mandatory2)
|
|
```
|
|
|
|
or the following, if you want to modify its behavior with optional parameters
|
|
|
|
```
|
|
obj.Method(mandatory1, mandatory2, optional1, optional2, optional3)
|
|
```
|
|
|
|
Intead of the more clunky zero value for optionals style
|
|
|
|
```
|
|
obj.Method(mandatory1, mandatory2, nil, "", 0)
|
|
```
|
|
|
|
or the equally cluncky config object style, which requires you to create a
|
|
struct with `NamesThatLookReallyLongBecauseItNeedsToIncludeMethodNamesConfig
|
|
|
|
```
|
|
cfg := &ConfigForMethod{
|
|
Optional1: ...,
|
|
Optional2: ...,
|
|
Optional3: ...,
|
|
}
|
|
obj.Method(mandatory1, mandatory2, &cfg)
|
|
```
|
|
|
|
# SYNOPSIS
|
|
|
|
This library is intended to be a reusable component to implement
|
|
a function with arguments that look like the following:
|
|
|
|
```
|
|
obj.Method(mandatory1, mandatory2, optional1, optional2, optional3, ...)
|
|
```
|
|
|
|
Internally, we just declare this method as follows:
|
|
|
|
```
|
|
func (obj *Object) Method(m1 Type1, m2 Type2, options ...Option) {
|
|
...
|
|
}
|
|
```
|
|
|
|
Option objects take two arguments, its identifier and the value it contains.
|
|
The identifier can be anything, but it's usually better to use a an unexported
|
|
empty struct so that only you have the ability to generate said option:
|
|
|
|
```
|
|
type identOptionalParamOne struct{}
|
|
type identOptionalParamTwo struct{}
|
|
type identOptionalParamThree struct{}
|
|
|
|
func WithOptionOne(v ...) Option {
|
|
return option.New(identOptionalParamOne{}, v)
|
|
}
|
|
```
|
|
|
|
Then you can call the method we described above as
|
|
|
|
```
|
|
obj.Method(m1, m2, WithOptionOne(...), WithOptionTwo(...), WithOptionThree(...))
|
|
```
|
|
|
|
Options should be parsed in a code that looks somewhat like this
|
|
|
|
```
|
|
func (obj *Object) Method(m1 Type1, m2 Type2, options ...Option) {
|
|
paramOne := defaultValueParamOne
|
|
for _, option := range options {
|
|
switch option.Ident() {
|
|
case identOptionalParamOne{}:
|
|
paramOne = option.Value().(...)
|
|
}
|
|
}
|
|
...
|
|
}
|
|
```
|
|
|
|
# Simple usage
|
|
|
|
Most of the times all you need to do is to declare the Option type as an alias
|
|
in your code:
|
|
|
|
```
|
|
package myawesomepkg
|
|
|
|
import "github.com/lestrrat-go/option"
|
|
|
|
type Option = option.Interface
|
|
```
|
|
|
|
Then you can start definig options like they are described in the SYNOPSIS section.
|
|
|
|
# Differentiating Options
|
|
|
|
When you have multiple methods and options, and those options can only be passed to
|
|
each one the methods, it's hard to see which options should be passed to which method.
|
|
|
|
```
|
|
func WithX() Option {}
|
|
func WithY() Option {}
|
|
|
|
// Now, which of WithX/WithY go to which method?
|
|
func (*Obj) Method1(options ...Option) {}
|
|
func (*Obj) Method2(options ...Option) {}
|
|
```
|
|
|
|
In this case the easiest way to make it obvious is to put an extra layer around
|
|
the options so that they have different types
|
|
|
|
```
|
|
type Method1Option interface {
|
|
Option
|
|
method1Option()
|
|
}
|
|
|
|
type method1Option struct { Option }
|
|
func (*method1Option) method1Option() {}
|
|
|
|
func WithX() Method1Option {
|
|
return &methodOption{option.New(...)}
|
|
}
|
|
|
|
func (*Obj) Method1(options ...Method1Option) {}
|
|
```
|
|
|
|
This way the compiler knows if an option can be passed to a given method.
|