---
id: ebf46089667a6469b258b8e52e57571f
author: Yunjin Lee
title: 'Interfacce Go e struct embedding: differenze rispetto all''ereditarietà di classe e progettazione basata sulla composizione'
description: Confronta le differenze tra l'ereditarietà di classi in Java/C++ e la tipizzazione strutturale delle interfacce in Go, e illustra con esempi di codice il pattern di composizione (Composition) basato sull'embedding di strutture e sulla delega dei metodi.
language: it
date: 2025-07-17T10:40:02.284435348Z
path: /blog/posts/go-interfaces-are-not-inheritance-zd04c1084
category: language
---

# Panoramica

Le interfacce di Go permettono di avere facilmente, in più strutture, funzioni con gli stessi argomenti e valori di ritorno, ma questo non corrisponde al modo in cui la parola chiave extends di Java estende e sovrascrive in modo appropriato il comportamento delle funzioni interne. Solo comprendendo correttamente il riuso compositivo del codice in Go è possibile non confonderlo con l'ereditarietà, ma è difficile raggiungere subito una comprensione teorica perfetta. Vediamo insieme alcuni scenari in cui è facile commettere errori.

## Errori frequenti

I principianti possono commettere i seguenti errori.

```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()
}
```

Un codice del genere sembra non avere problemi se si crede che Go segua l'ereditarietà tradizionale. Tuttavia, il suo output è il seguente.

```
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
```

Qui il comportamento di Go diventa semplicemente chiaro.

```go
watermelon := apple
```

Questo codice non converte affatto Apple nella classe Watermelon.
Semplicemente, watermelon è soltanto un puntatore ad apple.

Lo ribadiamo: **Go non segue il concetto tradizionale di ereditarietà.**

Scrivere codice con questo fraintendimento porta a errori critici, come la creazione di puntatori inutili o la copia di funzioni in strutture non previste.

Quale sarebbe allora un codice esemplare?

## Esempio appropriato in 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")
}
```

Tuttavia, in Go è possibile anche far apparire qualcosa **come** ereditarietà.
È l'esempio dell'incorporamento anonimo. Ciò è possibile dichiarando la struttura interna come struttura senza nome.
In questo caso, si può accedere direttamente ai campi della sottostruttura **anche senza specificarla esplicitamente**.
Utilizzando questo schema di **promozione** dei campi della sottostruttura alla struttura superiore, in alcuni casi si può migliorare la leggibilità. Tuttavia, si sconsiglia di usarlo quando è necessario mostrare esplicitamente la sottostruttura.

```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")
}
```

In questo esempio c'è la seguente differenza.

```go
w.PrintAll() // Non w.Friut.PrintAll(), ma una chiamata automatica promossa tramite la struttura senza nome
```

Entrambi gli esempi condividono questi punti fondamentali:

- main sintetico, funzioni suddivise per funzionalità
- strutture diverse richiedono oggetti diversi
- per la condivisione, utilizzare una struttura interna

Quali vantaggi offre questa filosofia di programmazione?

## Vantaggi

- Distinzione chiara tra metodi condivisi e non condivisi
- Separazione delle responsabilità tra strutture e metodi individuali
- Codice strutturalmente separato in base alle specifiche delle funzionalità richieste

All'inizio Go può risultare poco familiare perché differisce dall'OOP tradizionale, ma una volta acquisita familiarità diventa possibile una programmazione esplicita.

## Riepilogo

- Isoliamo le responsabilità
- Suddividiamo nel dettaglio per unità di struttura
- I metodi non devono essere intesi come le classi astratte di Java
- Programmiamo in modo esplicito e concreto
  Go deve essere trattato come un modello più semplice, chiaro e modulare rispetto al modello OOP tradizionale. Anziché programmare in modo espansivo, scriviamo il codice separandolo in modo graduale e strutturato.
