Funciones e Interfaces: El Modelo de Composición de Go
Go comparte comportamiento a través de funciones, métodos e interfaces en lugar de herencia de clases.
Busca en todas las páginas de la documentación
Go comparte comportamiento a través de funciones, métodos e interfaces en lugar de herencia de clases.
Ese diseño mantiene los tipos pequeños, las dependencias explícitas y las API fáciles de probar sin magia de framework profunda.
Los lenguajes orientados a objetos tradicionales a menudo modelan relaciones "es-un" con herencia: un HTTPServer es un Server, que es un Listener.
Go omite deliberadamente ese mecanismo.
En cambio, un tipo contiene datos (campos de struct) y los métodos adjuntan comportamiento a través de receptores.
Cuando dos tipos necesitan comportamiento compartido, extraes una función o defines una interfaz pequeña que ambos satisfacen.
Las interfaces en Go son conjuntos de firmas de métodos.
Un tipo concreto implementa una interfaz automáticamente cuando tiene los métodos requeridos: sin palabra clave implements, sin paso de generación de código.
Eso mantiene a los productores desacoplados de los consumidores: el paquete del manejador HTTP define type Handler interface { ServeHTTP(...) }; tu struct simplemente agrega el método.
La incrustación (campos de struct o interfaz anónimos) es la otra palanca de composición de Go.
La incrustación promueve métodos al tipo externo, similar a la delegación, pero no es herencia: no hay garantía de sustituibilidad más allá del conjunto de métodos promovidos.
Una analogía útil: los tipos de Go son ladrillos LEGO con espigas (métodos); las interfaces son la forma del agujero que necesitas.
Cualquier ladrillo con espigas coincidentes encaja, independientemente del color o la estructura interna.
El flujo de control en los servicios de Go típicamente se organiza así:
┌──────────────┐ depende de ┌──────────────┐
│ handler │ ────────────────► │ interface │
│ (concreto) │ │ (consumidor)│
└──────┬───────┘ └──────▲───────┘
│ llama │ satisfecho por
▼ │
┌──────────────┐ ┌──────┴───────┐
│ service │ │ repository │
│ struct │ │ mock / pg │
└──────────────┘ └──────────────┘
Las funciones son valores de primera clase: puedes pasarlas, devolverlas y almacenarlas en variables.
Eso permite cadenas de middleware (func(http.Handler) http.Handler), políticas de reintento y dobles de prueba sin código repetitivo de interfaz cuando una sola función es suficiente.
Los métodos vinculan el comportamiento a un tipo y participan en conjuntos de métodos.
El conjunto difiere para receptores de puntero vs. valor, lo que afecta la satisfacción de la interfaz, un detalle que perjudica a los equipos que mezclan estilos de receptor en un tipo.
Las interfaces se satisfacen implícitamente en tiempo de compilación.
El compilador verifica las asignaciones; el tiempo de ejecución utiliza búsquedas de itable para interfaces no vacías (par tipo + palabra de datos).
La interfaz vacía interface{} (o any) contiene cualquier valor pero sacrifica la verificación estática; prefiere interfaces tipadas en los límites.
La frase idiomática "aceptar interfaces, devolver structs" codifica la disciplina de composición: los llamadores dependen del comportamiento más pequeño que necesitan; los constructores devuelven tipos concretos para que los llamadores no queden bloqueados en la forma de tu interfaz.
// Consumer define el contrato.
type Clock interface {
Now() time.Time
}
// Producer devuelve un tipo concreto; las pruebas intercambian a través del parámetro de interfaz.
type RealClock struct{}
func (RealClock) Now() time.Time { return time.Now() }Las bases de código grandes de Go se mantienen saludables cuando las reglas de composición se aplican en la revisión, en lugar de dejarse a la intuición.
| Enfoque | Fortaleza | Debilidad | Mejor Ajuste |
|---|---|---|---|
| Interfaces de consumidor pequeñas | Falsificaciones fáciles, mocks estables | Muchas interfaces nombradas | Bibliotecas, servicios de dominio |
Parámetros de función (func(...)) | Mínima ceremonia | Más difícil componer API de múltiples métodos | Middleware, hooks |
| Incrustación de structs concretos | Reutilización rápida de valores predeterminados | Superficie de API promovida oculta | Envoltorios de http.Server, decoradores |
| Genéricos con restricciones | Algoritmos compartidos | Puede oscurecer la intención si se abusa | Colecciones, analizadores |
any en los límites | Interoperabilidad con datos dinámicos | Pierde seguridad en tiempo de compilación | Solo para decodificación JSON provisional |
Los frameworks gRPC y HTTP (chi, gin, echo) se componen a través de funciones y middleware con forma de interfaz.
Tus paquetes de negocio aún deben exponer interfaces de dominio, no tipos de framework, para que el cambio de enrutadores no reescriba la lógica central.
Los reconciliadores de Kubernetes controller-runtime se componen a través de interfaces (client.Client, reconciler.Reconciler) mientras que los structs incrustan client.Client para delegación, un patrón de composición canónico a gran escala.
Pruebas: reemplaza las interfaces con falsificaciones que registran las llamadas.
Si una interfaz crece más allá de ~3 métodos, divídela o pasa funciones por grupo de métodos.
Versionado: las interfaces exportadas son compromisos de compatibilidad.
Agregar métodos rompe a los implementadores; prefiere nombres de interfaz nuevos (Reader + ReadCloser) o interfaces no exportadas satisfechas por structs exportados.
== nil; esto rompe muchas protecciones if x == nil.Evita jerarquías de clases base frágiles y sobrescrituras ocultas.
Los equipos componen el comportamiento explícitamente con funciones, interfaces pequeñas e incrustación para que los cambios permanezcan locales y probables.
Un struct almacena datos.
Una interfaz describe el comportamiento (firmas de métodos) y contiene un par de tipo y valor en tiempo de ejecución cuando se asigna.
Las interfaces no tienen campos propios.
Usa métodos cuando el comportamiento esté ligado al ciclo de vida de un tipo o cuando necesites un conjunto de métodos para interfaces.
Usa funciones a nivel de paquete cuando la operación sea genérica y no necesite estado del receptor.
Los parámetros de función deben ser la interfaz más pequeña que los llamadores necesiten.
Devuelve structs concretos (o tipos nombrados) para que los usuarios no se vean obligados a depender de tus definiciones de interfaz.
Los campos anónimos incrustados promueven métodos al tipo externo.
Reutilizas la implementación sin subclasificar, pero los métodos promovidos exportados se convierten en parte de tu API pública; incrusta deliberadamente.
Desacoplamiento: los productores no deben importar paquetes de consumidores solo para declarar satisfacción.
El compilador verifica los conjuntos de métodos en los sitios de asignación en su lugar.
Los conjuntos de métodos incluyen métodos de valor tanto para T como para *T, pero solo métodos de puntero en *T.
La satisfacción de la interfaz depende de qué métodos se requieren y qué tipo de receptor asignas.
Los parámetros de tipo pueden ser restringidos por interfaces (interface { ~int | ~int64 } o restricciones estándar).
Usa genéricos para algoritmos compartidos entre tipos; usa interfaces para polimorfismo en tiempo de ejecución y límites de E/S.
No siempre; es apropiado en los límites de decodificación.
Evita any en las API de dominio donde una interfaz tipada o un struct comunican la intención a los lectores y al compilador.
Los manejadores son funciones o tipos con ServeHTTP.
Compón preocupaciones transversales con middleware func(http.Handler) http.Handler en lugar de clases de manejador base.
Exportar interfaces amplias desde paquetes productores.
Eso bloquea a los consumidores con mocks grandes y hace que la evolución de la API sea dolorosa.
main conecta servidores concretos y pasa context.Context a través de interfaces.
La composición mantiene la lógica de apagado en un solo lugar en lugar de cadenas de sobrescritura dispersas.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (predeterminado Green Tea GC, go fix modernizers - verificar parche en la compilación), chi (última versión - verificar en la compilación), gin (última versión - verificar en la compilación), echo (última versión - verificar en la compilación), google.golang.org/grpc (última versión - verificar en la compilación), sigs.k8s.io/controller-runtime (última versión - verificar en la compilación), kubebuilder (última versión - verificar en la compilación), tinygo (última versión - verificar objetivos de placa en la compilación), wazero (última versión - verificar en la compilación) y golangci-lint (última versión - verificar conjunto de linters en la compilación).
Revisado por Chris St. John·Última actualización: 18 jul 2026