---
id: ebf46089667a6469b258b8e52e57571f
author: Yunjin Lee
title: 'Go-grænseflader og strukturindlejring: forskelle fra klassearv og komposition som designmønster'
description: Denne tekst sammenligner klassearv i Java/C++ med den strukturelle typning i Go-interfaces og forklarer kompositionsmønsteret ved hjælp af struktur-embedding og metodedelegering med kodeeksempler.
language: da
date: 2025-07-17T10:40:02.284435348Z
path: /blog/posts/go-interfaces-are-not-inheritance-zd04c1084
category: language
---

# Oversigt

Go-interfaces gør det let for flere structs at have funktioner med samme argumenter og returværdier, men det fungerer anderledes end Javas extends-nøgleord, som udvider og overskriver funktionernes adfærd. Man må forstå Gos kompositionsbaserede kodegenbrug korrekt for ikke at forveksle det med arv, men det er svært at opnå en teoretisk fuldstændig forståelse fra starten. Lad os se på det sammen med scenarier, hvor man let begår fejl.

## Almindelige fejl

Nybegyndere kan begå følgende fejl.

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

Hvis man tror, at Go følger traditionel arv, virker denne kode uden problemer. Dens outputresultat er dog følgende.

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

Her bliver Gos adfærd blot tydelig.

```go
watermelon := apple
```

Denne kode konverterer på ingen måde Apple direkte til Watermelon-klassen.
Watermelon er blot en pointer til apple.

Det skal understreges igen: **Go følger ikke det traditionelle arvebegreb.**

Hvis man skriver kode med denne misforståelse, opstår alvorlige fejl såsom meningsløs oprettelse af pointere og uventet kopiering af funktioner til andre structs.

Hvordan ser en mønstergyldig kode så ud?

## Passende eksempel i 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")
}
```

Det er dog muligt i Go at få det til at ligne arv **på en måde**.
Det er eksemplet med anonym embedding. Dette opnås ved at deklarere den indre struct uden navn.
I dette tilfælde kan man få direkte adgang til feltene i den underordnede struct **uden at angive dem eksplicit**.
Ved at bruge mønsteret, hvor felterne i den underordnede struct **forfremmes** til den overordnede struct, kan læsbarheden i nogle tilfælde forbedres. Hvis den underordnede struct skal vises eksplicit, anbefales det dog ikke at bruge dette.

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

I dette eksempel er der følgende forskel.

```go
w.PrintAll() // w.Friut.PrintAll()이 아닌, 이름 없는 구조체를 통한 자동 승격 호출
```

Begge eksempler har følgende vigtige punkter.

- Hold main enkel, og opdel funktioner efter funktionalitet
- Brug forskellige objekter til forskellige structs
- Brug en intern struct, når der er behov for deling

Hvilke fordele har denne programmeringsfilosofi?

## Fordele

- Tydelig adskillelse mellem metoder, der skal deles, og dem, der ikke skal
- Adskillelse af ansvar mellem individuelle structs og metoder
- Kode, der er strukturelt adskilt efter de nødvendige funktionelle specifikationer

Til at begynde med kan Go føles uvant, da det adskiller sig fra traditionel OOP, men når man vænner sig til det, bliver eksplicit programmering mulig.

## Resume

- Isoler ansvarsområder
- Opdel detaljeret på struct-niveau
- Metoder må ikke forstås som abstrakte klasser i Java
- Programmer eksplicit og konkret
  Go bør behandles mere enkelt og tydeligt end den traditionelle OOP-model, og hvert enkelt element bør håndteres separat. Skriv hellere trinvist og struktureret med adskilte dele end ekspansivt.
