SoatDev IT Consulting
SoatDev IT Consulting
  • About us
  • Expertise
  • Services
  • How it works
  • Contact Us
  • News
  • May 31, 2023
  • Rss Fetcher

An easy way to design extensible constructors that accept an arbitrary number of arguments

Photo by Vladislav Babienko on Unsplash

The options pattern is a way to design extensible constructors that accept an arbitrary number of arguments. Let’s get started with this code:

type person struct {
age int
hairColour string
}

// the constructor using the "option" function
func NewPerson(opts ...PersonOption) *person {
p := new(person)
for _, opt := range opts {
opt(p) // calls the option
}
}

// the "option" function
type PersonOption func(p *person)

func withHairColour(colour string) PersonOption {
return func(p *person) {
p.hairColour = colour
}
}

func withAge(age int) PersonOption {
return func(p *person) {
p.age = age
}
}

func main() {
person := NewPerson(withHairColour("light brown"), withAge(30))
// person.hairColour is now light brown
// person.age is now 30
}

Its main building block is the option function (type PersonOption func(p *person), which accepts a single argument, namely the value to modify (p *person). The functions withHairColour and withAge are both higher-order functions (HOFs) that return the PersonOption function.

It’s also possible to implement the options pattern using single-method interfaces (SMIs). The trick is to replace the option function with an SMI. This leads to a choice: do you want to use HOFs such as withHairColour or define types that implement the option SMI?

We want a flexible function for building responses in an HTTP controller. And we want to use HOFs to achieve this. The following example demonstrates how to achieve this:

// the "option" SMI
type responseOption interface {
apply(w http.ResponseWriter)
}

// an adapter type similar to http.HandlerFunc whose single purpose
// is to allow functions to be used as the responseOption interface
type responseOptionFunc func(w http.ResponseWriter)

func (fn responseOptionFunc) apply(w http.ResponseWriter) {
fn(w)
}

// HOF that sets the response's HTTP status code
func withStatusCode(code int) responseOption {
return responseOptionFunc(func(w http.ResponseWriter){
w.WriteHeader(code)
})
}

// HOF that marshalls a JSON object into bytes and writes them to the output.
// The HOF also sets the "content-type" to an appropriate value for a JSON
// payload.
func withJSON(obj any) responseOption {
return responseOptionFunc(func(w http.ResponseWriter){
w.Header().Set("Content-Type", "application/json")
b, _:= json.Marshal(obj)
w.Write(b)
})
}

// The API we want to achieve. A flexible function that can respond
// in any way depending only on the passed opts.
func respond(w http.ResponseWriter, opts ...responseOption) {
for _, opt := range opts {
opt.apply(w)
}
}

// controller method usage example
func (ctlr *controller) listResource(w http.ResponseWriter, r *http.Request) {
// implementation omitted for brevity
respond(withStatusCode(200), withJSON(obj))
}

Another option is to omit defining the adapter type and not use HOFs at all. This is presented below:

// the "option" SMI
type responseOption interface {
apply(w http.ResponseWriter)
}

// instead of a HOF we use a type that implements the "option" SMI.
type withStatusCode int

func (c withStatusCode) apply(w http.ResponseWriter) {
w.WriteHeader(c)
}

type withJSON struct {
obj any
}

func (json withJSON) apply(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json")
b, _:= json.Marshal(json.obj)
w.Write(b)
}

// The API we want to achieve. A flexible function that can respond
// in any way depending only on the passed opts.
func respond(w http.ResponseWriter, opts ...responseOption) {
for _, opt := range opts {
opt.apply(w)
}
}

// controller method usage example
func (ctlr *controller) listResource(w http.ResponseWriter, r *http.Request) {
// implementation omitted for brevity
respond(withStatusCode(200), withJSON{obj})
}

Conclusion

It’s possible to use SMIs to implement the options pattern instead of the purely functional approach. In most cases, which to choose is a matter of personal preference. Using SMIs results in slightly more verbose code but allows for more freedom since the “option” may be any data structure that fulfills the option interface. On the other hand, if a function is all you need, that’s what you should use for the option definition.


The Options Pattern Using Single-Method Interfaces was originally published in Better Programming on Medium, where people are continuing the conversation by highlighting and responding to this story.

Previous Post
Next Post

Recent Posts

  • Left-leaning influencers embrace Bluesky without abandoning X, Pew says
  • NAACP calls on Memphis officials to halt operations at xAI’s ‘dirty data center’
  • Meta plans to automate many of its product risk assessments
  • The ellipse hidden inside Pascal’s triangle
  • Week in Review: Perplexity Labs wants to do your work

Categories

  • Industry News
  • Programming
  • RSS Fetched Articles
  • Uncategorized

Archives

  • May 2025
  • April 2025
  • February 2025
  • January 2025
  • December 2024
  • November 2024
  • October 2024
  • September 2024
  • August 2024
  • July 2024
  • June 2024
  • May 2024
  • April 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • September 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • April 2023

Tap into the power of Microservices, MVC Architecture, Cloud, Containers, UML, and Scrum methodologies to bolster your project planning, execution, and application development processes.

Solutions

  • IT Consultation
  • Agile Transformation
  • Software Development
  • DevOps & CI/CD

Regions Covered

  • Montreal
  • New York
  • Paris
  • Mauritius
  • Abidjan
  • Dakar

Subscribe to Newsletter

Join our monthly newsletter subscribers to get the latest news and insights.

© Copyright 2023. All Rights Reserved by Soatdev IT Consulting Inc.