La composición de interfaces en Go es el mecanismo de diseño estructural que permite definir una interfaz compleja mediante la inclusión o “embebido” de una o más interfaces preexistentes dentro de su declaración. A diferencia de la herencia jerárquica en lenguajes orientados a objetos tradicionales, este proceso no establece una relación de parentesco, sino que expande el conjunto de métodos (method set) de la interfaz resultante para que sea la unión de todos los métodos definidos en las interfaces embebidas.
Este comportamiento existe en Go para fomentar la creación de abstracciones pequeñas, atómicas y altamente reutilizables, resolviendo el problema de la fragilidad de las jerarquías profundas de clases. Al favorecer la composición sobre la herencia, Go permite que los desarrolladores definan contratos de comportamiento granulares (como io.Reader o io.Writer) que luego pueden combinarse de forma flexible según las necesidades del consumidor, facilitando un desacoplamiento superior y una mayor cohesión en el código.
Mecánica del Method Set y Asignabilidad
Desde una perspectiva interna, cuando una interfaz embebe a otra, el compilador de Go realiza una unión de firmas de métodos. La interfaz resultante adquiere todos los métodos de las interfaces internas, y cualquier tipo que desee satisfacer esta interfaz compuesta debe implementar la totalidad de los métodos presentes en el conjunto expandido. La assignability de un tipo concreto a una interfaz compuesta se evalúa de la misma manera que en una interfaz simple: el tipo debe poseer todos los métodos requeridos en su propio method set.
La biblioteca estándar (stdlib) utiliza este patrón como piedra angular de su arquitectura. Por ejemplo, el paquete io define interfaces de un solo método que son la base de operaciones de entrada y salida en todo el ecosistema. La interfaz io.ReadCloser no es más que la unión de io.Reader e io.Writer, lo que permite que funciones que solo necesitan cerrar un flujo no dependan de la lógica de lectura, mientras que aquellas que necesitan ambas operaciones utilicen la interfaz compuesta.
package main
import (
"errors"
"fmt"
"io"
)
// Definición de interfaces simples (atómicas)
type Procesador interface {
Procesar(data []byte) ([]byte, error)
}
type Almacenador interface {
Guardar(data []byte) error
}
// Composición de interfaces: Embebido técnico
// El method set de Pipeline es la unión de Procesador y Almacenador
type Pipeline interface {
Procesador
Almacenador
}
type MotorBusqueda struct {
ID string
}
func (m *MotorBusqueda) Procesar(data []byte) ([]byte, error) {
return append([]byte("PROCESADO: "), data...), nil
}
func (m *MotorBusqueda) Guardar(data []byte) error {
fmt.Printf("Guardando: %s\n", string(data))
return nil
}
func EjecutarFlujo(p Pipeline, payload []byte) error {
res, err := p.Procesar(payload)
if err != nil {
return err
}
return p.Guardar(res)
}
func main() {
m := &MotorBusqueda{ID: "Elastic-01"}
// MotorBusqueda satisface Pipeline porque implementa ambos métodos
err := EjecutarFlujo(m, []byte("datos_crudos"))
if err != nil {
fmt.Println("Error:", err)
}
}
GoEl comportamiento más contraintuitivo del embebido ocurre en la relación de subconjuntos: cualquier tipo que satisfaga una interfaz compuesta satisface automáticamente sus interfaces componentes de forma individual, pero lo opuesto no es cierto. Esto permite que un objeto sea pasado a funciones que esperan solo una parte del comportamiento, reduciendo la superficie de contacto necesaria para la ejecución.
Superposición de métodos y colisiones de firmas
Un comportamiento técnico específico del compilador se manifiesta cuando dos interfaces embebidas contienen métodos con el mismo nombre. Antes de Go 1.14, la superposición de métodos con el mismo nombre en una interfaz compuesta generaba un error de compilación inmediato por duplicación de métodos. Sin embargo, en versiones actuales, Go permite el embebido de interfaces que comparten métodos, siempre que las firmas de dichos métodos sean idénticas.
type Lector interface {
Leer() string
}
type Escaneador interface {
Leer() string // Firma idéntica
}
// Válido desde Go 1.14
type Dispositivo interface {
Lector
Escaneador
}
GoEl edge case surge cuando las firmas difieren ligeramente (por ejemplo, en el tipo de retorno o parámetros). En este escenario, el compilador no puede resolver la ambigüedad del conjunto de métodos y el tipo resultante será inválido, lanzando un error de “duplicate method”. Esta restricción garantiza que no existan colisiones semánticas al invocar el método a través de la interfaz compuesta, manteniendo la integridad del despacho dinámico de métodos en el runtime.
- Módulo: Métodos e Interfaces
- Artículo número: #84