Encadenamiento de métodos
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 81 de 107.
El encadenamiento de métodos (method chaining) es una técnica en la que los métodos devuelven el receptor, lo que permite vincular múltiples llamadas a métodos en una sola sentencia. Ya has visto esto en el patrón Builder, pero es un concepto independiente que resulta útil en muchos contextos.
La clave es devolver un puntero al struct desde cada método:
type Query struct {
table string
columns []string
where string
limit int
}
func (q *Query) Select(cols ...string) *Query {
q.columns = cols
return q
}
func (q *Query) From(table string) *Query {
q.table = table
return q
}
func (q *Query) Where(condition string) *Query {
q.where = condition
return q
}
func (q *Query) Limit(n int) *Query {
q.limit = n
return q
}Ahora puedes encadenar estas llamadas de forma fluida:
query := &Query{}
query.Select("name", "email").From("users").Where("active = true").Limit(10)Esto se lee casi como un lenguaje natural en comparación con las sentencias por separado. El encadenamiento de métodos funciona mejor cuando las operaciones configuran o modifican un objeto sin necesidad de devolver otros valores. Se utiliza comúnmente en constructores de consultas, APIs de configuración y bibliotecas de aserciones de prueba.
Recuerda que cada método debe usar un receptor de puntero y devolver *StructType para que el encadenamiento funcione correctamente.
Desafío
Fácil¡Vamos a construir un generador de elementos HTML fluido que utiliza el encadenamiento de métodos para construir etiquetas HTML con atributos y contenido! Esta es una aplicación práctica del encadenamiento de métodos: crearás una API que se lee casi como lenguaje natural al construir elementos HTML.
Organizarás tu código en dos archivos:
element.go: Define tu generador de elementos HTML con métodos encadenables.Crea una estructura
Elementque almacene el nombre de la etiqueta, un mapa de atributos (string a string) y el contenido interno. Construye estos métodos encadenables que devuelven cada uno*Element:NewElement(tag string)- constructor que crea un nuevo Element con el nombre de etiqueta dado y un mapa de atributos inicializado (vacío)Attr(key, value string)- añade un atributo al elementoID(id string)- método de conveniencia que establece el atributo "id"Class(class string)- método de conveniencia que establece el atributo "class"Content(text string)- establece el contenido interno del elemento
Añade un método
Render()que devuelva la cadena HTML. Los atributos deben aparecer en orden alfabético por clave. El formato es:<tag attr1="value1" attr2="value2">content</tag>Si no hay atributos, simplemente renderiza
<tag>content</tag>. Si no hay contenido, renderiza<tag attr="value"></tag>.main.go: Construye elementos HTML utilizando el encadenamiento de métodos.Lee el nombre de la etiqueta, luego lee un recuento de operaciones a realizar. Cada operación es una de las siguientes:
idseguido del valor del IDclassseguido del valor de la claseattrseguido del nombre del atributo y el valor (dos líneas)contentseguido del texto del contenido
Encadena todas las operaciones de forma fluida, luego imprime el HTML renderizado.
Se proporcionarán las siguientes entradas:
- Línea 1: Nombre de la etiqueta
- Línea 2: Número de operaciones
- Líneas siguientes: Tipo de operación y valor(es)
Por ejemplo, dado:
div
3
id
container
class
main-content
content
Hello WorldTu salida debería ser:
<div class="main-content" id="container">Hello World</div>Y dado:
a
3
attr
href
https://example.com
attr
target
_blank
content
Click hereTu salida debería ser:
<a href="https://example.com" target="_blank">Click here</a>Y dado:
input
2
attr
type
text
attr
placeholder
Enter nameTu salida debería ser:
<input placeholder="Enter name" type="text"></input>Y dado:
p
1
content
Simple paragraphTu salida debería ser:
<p>Simple paragraph</p>Observa cómo el encadenamiento de métodos te permite construir elementos paso a paso de una manera legible. Cada método modifica el elemento y lo devuelve, permitiendo que la siguiente llamada al método se encadene directamente.
Hoja de referencia
El encadenamiento de métodos es una técnica en la que los métodos devuelven el receptor (normalmente *StructType), lo que permite vincular múltiples llamadas a métodos en una sola sentencia.
Cada método debe usar un receptor de puntero y devolver *StructType:
type Query struct {
table string
columns []string
where string
limit int
}
func (q *Query) Select(cols ...string) *Query {
q.columns = cols
return q
}
func (q *Query) From(table string) *Query {
q.table = table
return q
}
func (q *Query) Where(condition string) *Query {
q.where = condition
return q
}
func (q *Query) Limit(n int) *Query {
q.limit = n
return q
}Encadene llamadas a métodos de forma fluida:
query := &Query{}
query.Select("name", "email").From("users").Where("active = true").Limit(10)El encadenamiento de métodos funciona mejor cuando las operaciones configuran o modifican un objeto sin necesidad de devolver otros valores. Se utiliza comúnmente en constructores de consultas (query builders), APIs de configuración y librerías de aserciones de prueba.
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Leer el nombre de la etiqueta
scanner.Scan()
tagName := scanner.Text()
// Leer el número de operaciones
scanner.Scan()
numOps, _ := strconv.Atoi(scanner.Text())
// Crear el elemento
element := NewElement(tagName)
// Procesar cada operación
for i := 0; i < numOps; i++ {
scanner.Scan()
operation := scanner.Text()
// TODO: Basado en el tipo de operación, encadenar el método apropiado
// Las operaciones pueden ser: "id", "class", "attr", o "content"
// - Para "id": leer la siguiente línea como el valor del ID
// - Para "class": leer la siguiente línea como el valor de la clase
// - Para "attr": leer las siguientes DOS líneas (nombre del atributo, luego el valor)
// - Para "content": leer la siguiente línea como el texto del contenido
// Usar el encadenamiento de métodos para construir el elemento de forma fluida
_ = operation // Eliminar esta línea al implementar
}
// Imprimir el HTML renderizado
fmt.Println(element.Render())
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de POO en Go
Archivos externosEspacio de trabajo y módulos de GoPaquetes e importacionesNombres exportados vs. no exportadosIntroducción a la POO en GoStructs como clasesDefinición de métodos en structsReceptores de puntero vs. de valorInicialización de structsFunciones constructorasResumen - Calculadora simple4Interfaces
Introducción a las interfacesImplementación implícitaLa interfaz como contratoInterfaz vacía (any)Type AssertionType SwitchComposición de interfacesInterfaces Stringer y ErrorResumen: Calculadora de figuras7Encapsulamiento
Campos exportados vs. no exportadosEncapsulamiento a nivel de paqueteMétodos Getter y SetterOcultamiento de información en GoResumen - Registros de estudiantes10Genéricos (Go 1.18+)
Introducción a los GenéricosParámetros de tipoRestricciones de tipoStructs genéricosSolución alternativa para métodos genéricosResumen - Colección genérica13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón Abstract FactoryPatrón ObserverPatrón Strategy2Inmersión profunda en Tipos y Structs
Tipos básicos y compuestosDefiniciones de tipos personalizadosEtiquetas de StructsStructs anónimosStructs anidadosValores cero y por defectoRepaso - Libreta de contactos5Composición sobre herencia
Por qué Go no tiene herenciaConceptos básicos de incrustación de structsPromoción de métodosIncrustación de múltiples structsIncrustación vs AgregaciónShadowing de métodos incrustadosResumen - Jerarquía de empleados8Manejo de Errores y OOP
La interfaz errorTipos de errores personalizadosEnvoltura de errores (fmt.Errorf)Errores centinelaerrors.Is() y errors.As()Panic, Defer y RecoverResumen - Analizador de archivos3Punteros y Memoria
Fundamentos de punteros en GoPunteros a StructsPaso por valor vs referenciaLa función new()Garbage Collection en GoResumen - Constructor de Linked List6Polimorfismo en Go
Polimorfismo mediante interfacesDuck Typing en GoReglas de satisfacción de interfacesColecciones polimórficasInyección de dependenciasResumen - Procesador de pagos9Concurrencia y POO
Fundamentos de GoroutinesChannels y comunicaciónChannels con buffer vs. sin bufferSentencia Selectsync.Mutex y sync.RWMutexsync.WaitGroupDiseño de estructuras Thread-SafeResumen - Worker Pool12Conceptos avanzados de POO
Patrón Functional OptionsPatrón Builder en GoEncadenamiento de métodosAlias de tipos vs. definicionesFundamentos de ReflectionDescripción general de generación de código