Golang Functional Options Pattern
Published: 2023-05-18 09:10:23


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:
```Go
type Student struct {
  Name string
  Age int
  Address *string // optional
  PhoneNumber *int // optional
}

func NewStudent(name string, address string, age int, phoneNumber int)
```

If all fields are required, instantiating a new student each time would be neat and easy to read:
```Go
student := NewStudent(name string, address string, age int, phoneNumber int)
```

Even if we needed 10x students, using the constructor method as above would be we all well too:
```Go
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)
```

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)
```Go
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)
...
```

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

```Go
// 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)
}
```