Skip to content

Functional Options

Posted on:February 13, 2021聽at聽12:58 PM

Table of Contents

Open Table of Contents

Introducci贸n

Recientemente me encontraba escribiendo un paquete en Go y me vi en la necesidad de proveer al usuario de mi paquete la posibilidad de pasar alg煤n tipo de configuraci贸n personalizada o bien usar las que mi paquete ya provee.

Al principio pens茅 en usar algunas variables globales (lo cual casi siempre es una mala idea 馃憥馃徑) las cuales el usuario pudiera sobrescribir o bien usar los valores que yo estableciera en esas variables. Sin embargo despu茅s de investigar un poco me encontr茅 con el concepto de Functional Options, veamos como usar esta t茅cnica.

El problema

Supongamos que queremos dise帽ar un paquete que nos permita saludar a un usuario en base a un nombre que ellos nos pasen, adem谩s queremos que los usuarios puedan pasarnos saludos personalizados o usar los que nosotros coloquemos por defecto.

Functional Options al rescate

Para esta soluci贸n lo que vamos a hacer es crear un tipo llamado Option, este es un enfoque de programaci贸n funcional el cual nos permite pasar n n煤mero de Options como funciones e ir aplicando esas opciones a la estructura que estemos usando, en este caso Greeter, esto nos da la oportunidad de establecer valores que el usuario nos pase (que es lo que queremos) o usar valores por defecto que nosotros decidamos.

// Package greeter handles greets for users.
package greeter

import "fmt"

// Greeter knows how to greet users.
type Greeter struct {
	Greeting string
	Message  string
}

// Option defines an option type function, to handle multiple options.
type Option func(*Greeter)

// WithGreeting knows how to set a custom Greeting for our Greeter.
func WithGreeting(greet string) Option {
	return func(g *Greeter) {
		g.Greeting = greet
	}
}

// WithMessage knows how to set a custom Message for our Greeter.
func WithMessage(message string) Option {
	return func(g *Greeter) {
		g.Message = message
	}
}

// New creates a new Greeter with some default values.
func New(options ...Option) *Greeter {
	g := &Greeter{
		Greeting: "Hello",
		Message:  "Nice to meet you!.",
	}

	for _, option := range options {
		option(g)
	}

	return g
}

// Greet knows how to greet an user.
func (g Greeter) Greet(name string, args ...string) {
	fmt.Printf("%s %s %s\n", g.Greeting, name, g.Message)
}

Una vez creado nuestro paquete greeter procedemos a usarlo en nuestro paquete main de la siguiente forma, en el primer ejemplo vemos como usar los valores por defecto que establecimos en el paquete greeter 煤nicamente llamando a la funci贸n New (sin ninguna opci贸n) y posteriormente llamando al m茅todo Greet con el valor de 鈥淛ohn Doe鈥.

En el segundo ejemplo vemos c贸mo pasar algunas function options al momento de llamar a la funci贸n New en este caso estamos estableciendo los valores que necesitemos para el Message y Greeting.

package main

import "github.com/gustavocd/code-club/options/greeter"

func main() {
	// Using default values provided by our Option functions.
	dg := greeter.New()
	dg.Greet("John Doe")

	// Using custom config values via our Option functions.
	g := greeter.New(
		greeter.WithGreeting("Hola"),
		greeter.WithMessage("Un placer conocerte!."),
	)
	g.Greet("Gustavo")
}

Recursos 煤tiles para aprender m谩s sobre este tema

Conclusi贸n

Es cierto que, con esta soluci贸n la legibilidad de nuestro c贸digo aumenta en complejidad, dado que no todos estamos familiarizados con la programaci贸n funcional y para los que son nuevos en Go puede llegar a ser complicado de entender.

Sin embargo, me parece una soluci贸n que nos da un buen balance entre legibilidad para el programador que crea el paquete y una buena experiencia de usuario para el programador que usa nuestro paquete, as铆 que de momento es un precio que estoy dispuesto a pagar.