DEV Community

Zaffere
Zaffere

Posted on

Golang Functional Options Pattern

The Functional Options Pattern is Creational Design Pattern which allows you to build complex structs using a constructor which takes in zero or more functions as arguments.

Sometimes we find ourselves having to deal with a domain object that takes in optional fields:

type Student struct {
  Name string
  Age int
  Address *string // optional
  PhoneNumber *int // optional
}

func NewStudent(name string, address string, age int, phoneNumber int)
Enter fullscreen mode Exit fullscreen mode

If all fields are required, instantiating a new student each time would be neat and easy to read:

student := NewStudent(name string, address string, age int, phoneNumber int)
Enter fullscreen mode Exit fullscreen mode

Even if we needed 10x students, using the constructor method as above would be we all well too:

student1 := NewStudent("Jon", "Kings Road", 12, 12345)
student2 := NewStudent("Tim", "Sixth Ave", 12, 11223)
student3 := NewStudent("Sara", "Orchard Road", 12, 22112)
student4 := NewStudent("Vanessa", "River Valley", 13, 43321)
student5 := NewStudent("Steve", "Killiney Road", 12, 55423)
Enter fullscreen mode Exit fullscreen mode

However, if we had 100 students new students and some provided their address and phone numbers while some don't, creating new students would get real messy real fast:
(With only 4 fields in the struct, this doesn't seem like a real problem however, in production, your domain model typically consists of more than just 4 fields which makes it easier to make mistakes and harder to read)

student1 := NewStudent("Jon", "River Valley", 12, 0)
student2 := NewStudent("Alex", "", 10, 22222)
student3 := NewStudent("Jones", "Kings Road", 12, 0)
student4 := NewStudent("Rachel", "", 12, 0)
student5 := NewStudent("Esther", "Killiney Road", 13, 55555)
...
Enter fullscreen mode Exit fullscreen mode

To help with this, we can use whats called an Optional method/function pattern

  1. Create a custom Type of type:function that takes in a pointer to the struct you want to build
  2. Expose a method that takes in those optional values and returns a function with the same function signature of the custom function type, this returned function of type:Option function would be responsible for adding those optional values to the struct
// Custom Type which is a function that takes in a pointer of the Student struct
type OptionalStudentValues func(s *Student)

func NewStudent(name string, age int, opts ...OptionalStudentValues) *Student {
    student := &Student{
        Name: name,
        Age:  age,
    }

    // Checks id there are optional values needed to be passed to the new Student struct
    if len(opts) > 0 {
        for _, opt := range opts {
            // execute the function retruned by the exposed methods, passing in the student struct
            opt(student)
        }
    }
    return student
}

// Implementation of the Optional function
func WithAddress(addr string) OptionalStudentValues {
    return func(s *Student) {
        s.Address = &addr
    }
}

func WithPhoneNumber(num int) OptionalStudentValues {
    return func(s *Student) {
        s.PhoneNumber = &num
    }
}

func main() {
    studentAddr := "Address string"
    studentPhoneNumber := 12345

    //  With optional fields
    s := NewStudent("Tim", 20, WithAddress(studentAddr), WithPhoneNumber(studentPhoneNumber))
    // No optional fields ...
    s2 := NewStudent("Mary", 12)

    log.Printf("This is the newly created student with optional fields : %+v", s)
    log.Printf("This is the newly created student with no optional fields : %+v", s2)
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)