---
id: ebf46089667a6469b258b8e52e57571f
author: Yunjin Lee
title: 'Interfaces y embedding de estructuras en Go: diferencias con la herencia de clases y diseño basado en composición'
description: Se comparan la herencia de clases de Java/C++ y la tipificación estructural de las interfaces de Go, y se explica mediante ejemplos de código el patrón de composición (Composition) basado en la incrustación de estructuras (struct embedding) y la delegación de métodos.
language: es
date: 2025-07-17T10:40:02.284435348Z
path: /blog/posts/go-interfaces-are-not-inheritance-zd04c1084
category: language
---

# Descripción general

Las interfaces de Go permiten que varias estructuras tengan con facilidad funciones con los mismos argumentos y valores de retorno, pero esto difiere de la forma en que Java utiliza la palabra clave extends para extender y sobrescribir adecuadamente el comportamiento de sus funciones internas. Solo cuando se comprende correctamente la reutilización compositiva de código en Go es posible no confundirla con la herencia, aunque desde el principio resulta difícil alcanzar una comprensión teórica completa. Veamos esto con escenarios en los que es fácil cometer errores.

## Errores frecuentes

Los principiantes pueden cometer los siguientes errores.

```go
package main
import (
	"fmt"
	"strings"
)

type Fruits interface {
	GetBrix() float64
	GetName() string
	SetLabel()
	GetLabel(string) string
	PrintAll()
}

type Apple struct {
	Label string
	Name  string
	Brix  float64
}

type Watermelon struct {
	Label string
	Name  string
	Brix  float64
}

func (a *Apple) PrintAll() {
	fmt.Printf("Fruit: %s, Label: %s, Brix: %v\n", a.Name, a.Label, a.Brix)
}

const (
	NO_LABEL = "EMPTY LABEL"
)

func (a *Apple) SetLabel(lbl string) {
	a.Brix 	= 14.5;
	a.Name 	= "apple";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, a.Name) {
		fmt.Println("Succeed: Label was ", lbl)
		a.Label = lbl;
	} else {
		fmt.Println("Failed: Label was ", lbl)
		a.Label = NO_LABEL;
	}
}

func (w *Watermelon) SetLabel(lbl string) {
	w.Brix = 10;
	w.Name = "watermelon";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, w.Name) {
		w.Label = lbl;
	} else {
		w.Label = NO_LABEL;
	}
}

func main() {
	fmt.Println("Inheritance test #1")
	apple := new(Apple)
	watermelon := apple
	apple.SetLabel("Apple_1")
	fmt.Println("Apple, before copied to Watermelon")
	apple.PrintAll()
	watermelon.SetLabel("WaterMelon_2")
	fmt.Println("Apple, after copied to Watermelon")
	apple.PrintAll()
	fmt.Println("Watermelon, which inherited Apple's Method")
	watermelon.PrintAll()
}
```

Este código parece no tener problemas si se supone que Go sigue la herencia tradicional. Sin embargo, su resultado de salida es el siguiente.

```
Inheritance test #1
Succeed: Label was  Apple_1
Apple, before copied to Watermelon
Fruit: apple, Label: Apple_1, Brix: 14.5
Failed: Label was  WaterMelon_2
Apple, after copied to Watermelon
Fruit: apple, Label: EMPTY LABEL, Brix: 14.5
Watermelon, which inherited Apple's Method
Fruit: apple, Label: EMPTY LABEL, Brix: 14.5
```

Aquí el comportamiento de Go solo se vuelve claro con lo siguiente.

```go
watermelon := apple
```

Este código no convierte en absoluto a Apple en la clase Watermelon.
Simplemente, watermelon es un puntero a apple.

Aquí lo volvemos a enfatizar: **Go no sigue el concepto tradicional de herencia.**

Si se escribe código con este malentendido, se producen errores graves, como la creación de punteros sin sentido o la copia de funciones para otras estructuras inesperadas.

Entonces, ¿cómo sería el código modelo?

## Ejemplo adecuado en Go

```go
package main
import (
	"fmt"
	"strings"
)

type Fruits interface {
	GetBrix() float64
	GetName() string
	SetLabel()
	GetLabel(string) string
	PrintAll()
}

type BaseFruit struct {
	Name  string
	Brix  float64
}

type Apple struct {
	Label string
	Fruit BaseFruit
}

type Watermelon struct {
	Label string
	Fruit BaseFruit

}

func (b *BaseFruit) PrintAll() {
	fmt.Printf("Fruit: %s, Brix: %v\n", b.Name, b.Brix)
}


const (
	NO_LABEL = "EMPTY LABEL"
)

func (a *Apple) SetLabel(lbl string) {
	a.Fruit.Brix 	= 14.5;
	a.Fruit.Name 	= "apple";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, a.Fruit.Name) {
		fmt.Println("Succeed: Label was ", lbl)
		a.Label = lbl;
	} else {
		fmt.Println("Failed: Label was ", lbl)
		a.Label = NO_LABEL;
	}
	fmt.Printf("Fruit %s label set to %s\n", a.Fruit.Name, a.Label);
	a.Fruit.PrintAll()
}

func (w *Watermelon) SetLabel(lbl string) {
	w.Fruit.Brix = 10;
	w.Fruit.Name = "Watermelon";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, w.Fruit.Name) {
		w.Label = lbl;
	} else {
		w.Label = NO_LABEL;
	}
	fmt.Printf("Fruit %s label set to %s\n", w.Fruit.Name, w.Label);
	w.Fruit.PrintAll()
}

func main() {
	apple := new(Apple)
	watermelon := new(Watermelon)
	apple.SetLabel("Apple_1")
	watermelon.SetLabel("WaterMelon_2")
}
```

Sin embargo, en Go también es posible hacer que parezca **como** una herencia.
Es el ejemplo de la incrustación anónima. Esto es posible declarando la estructura interna como una estructura sin nombre.
En este caso, **los campos de la subestructura se pueden acceder directamente sin especificarlos** explícitamente.
Usando este patrón que **promueve** los campos de la subestructura a la estructura superior, en algunos casos es posible mejorar la legibilidad. Sin embargo, se recomienda no usarlo cuando sea necesario mostrar explícitamente la subestructura.

```go
package main
import (
	"fmt"
	"strings"
)

type Fruits interface {
	GetBrix() float64
	GetName() string
	SetLabel()
	GetLabel(string) string
	PrintAll()
}

type BaseFruit struct {
	Name  string
	Brix  float64
}

type Apple struct {
	Label string
	BaseFruit
}

type Watermelon struct {
	Label string
	BaseFruit

}

func (b *BaseFruit) PrintAll() {
	fmt.Printf("Fruit: %s, Brix: %v\n", b.Name, b.Brix)
}


const (
	NO_LABEL = "EMPTY LABEL"
)

func (a *Apple) SetLabel(lbl string) {
	a.Brix 	= 14.5;
	a.Name 	= "apple";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, a.Name) {
		fmt.Println("Succeed: Label was ", lbl)
		a.Label = lbl;
	} else {
		fmt.Println("Failed: Label was ", lbl)
		a.Label = NO_LABEL;
	}
	fmt.Printf("Fruit %s label set to %s\n", a.Name, a.Label);
	a.PrintAll()
}

func (w *Watermelon) SetLabel(lbl string) {
	w.Brix = 10;
	w.Name = "Watermelon";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, w.Name) {
		w.Label = lbl;
	} else {
		w.Label = NO_LABEL;
	}
	fmt.Printf("Fruit %s label set to %s\n", w.Name, w.Label);
	w.PrintAll()
}

func main() {
	apple := new(Apple)
	watermelon := new(Watermelon)
	apple.SetLabel("Apple_1")
	watermelon.SetLabel("WaterMelon_2")
}
```

En este ejemplo existe la siguiente diferencia.

```go
w.PrintAll() // En lugar de w.Fruit.PrintAll(), llamada automática por promoción a través de la estructura sin nombre
```

Ambos ejemplos tienen los siguientes puntos importantes:

- main simple, funciones separadas por funcionalidad
- Para estructuras distintas, objetos distintos
- Cuando se necesita compartir, usar la estructura interna

¿Qué ventajas tiene esta filosofía de programación?

## Ventajas

- Distinción clara entre los métodos que requieren compartirse y los que no
- Separación de responsabilidades entre estructuras y métodos individuales
- Código separado estructuralmente según la especificación de las funciones necesarias

Al principio, Go puede resultar poco familiar porque difiere de la POO tradicional, pero una vez acostumbrado, es posible programar de forma explícita.

## Resumen

- Aísla las responsabilidades
- Divide en detalle por unidad de estructura
- No debes entender los métodos como las clases abstractas de Java
- Programa de forma explícita y concreta
  Go es más simple y directo que el modelo de POO tradicional y debe tratarse de manera individual. En lugar de programar de forma extensible, escribamos separándolo por etapas y de manera estructurada.
