---
id: ebf46089667a6469b258b8e52e57571f
author: Yunjin Lee
title: 'Go-grensesnitt og strukturinnbygging: forskjeller fra klassearv og komposisjonsdesign'
description: Denne teksten sammenligner klassearv i Java/C++ med den strukturelle typingen i Go-grensesnitt, og forklarer, med kodeeksempler, komposisjonsmønsteret (Composition) basert på struktureinbygging (struct embedding) og metodedelegering.
language: "no"
date: 2025-07-17T10:40:02.284435348Z
path: /blog/posts/go-interfaces-are-not-inheritance-zd04c1084
category: language
---

# Oversikt

Go-grensesnitt (interfaces) gjør det enkelt for flere structer å ha funksjoner med samme argumenter og returverdier, men dette fungerer annerledes enn måten Javas extends-nøkkelord utvider og overstyrer oppførselen til de interne funksjonene på. Man må forstå Gos komposisjonsbaserte kodegjenbruk ordentlig for ikke å forveksle det med arv, men det er vanskelig å oppnå en teoretisk fullstendig forståelse fra starten. La oss se nærmere på dette med scenarioer der det er lett å gjøre feil.

## Vanlige feil

Nybegynnere kan gjøre følgende feil.

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

Dette kan se uproblematisk ut dersom man tror at Go følger tradisjonell arv. Utdataene fra koden er imidlertid som følger.

```
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 blir Gos oppførsel først tydelig.

```go
watermelon := apple
```

Denne koden konverterer ikke Apple til Watermelon-klassen. Watermelon er ganske enkelt en peker til apple.

Her gjentar vi igjen: **Go følger ikke det tradisjonelle arvekonseptet.**

Dersom man koder med denne misforståelsen, oppstår alvorlige feil som unødvendig opprettelse av pekere og kopiering av funksjoner til uvedkommende structer.

Hva er da en mønstergyldig kodeløsning?

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

Likevel er det mulig å få Go til å **se ut som** arv.
Det gjelder anonym innbygging (embedding). Dette gjøres ved å deklarere den indre structen uten navn. I så fall kan man få tilgang til feltene i underordnet struct **uten å spesifisere dem eksplisitt**.
Ved å bruke dette mønsteret, der feltene i underordnet struct blir **promotert** til den overordnede structen, kan lesbarheten i noen tilfeller forbedres. Man bør likevel unngå det dersom den underordnede structen må vises eksplisitt.

```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 eksemplet finnes følgende forskjell.

```go
w.PrintAll() // I stedet for w.Friut.PrintAll(), automatisk promotering kall via den anonyme structen
```

Begge eksemplene har følgende viktige poenger:

- Hold main enkel, og del funksjonene etter funksjonalitet
- Ulike structer bør ha ulike objekter
- Bruk indre structer når deling er nødvendig

Hvilke fordeler finnes det med denne programmeringsfilosofien?

## Fordeler

- Tydelig skille mellom metoder som må deles og de som ikke må det
- Ansvar isoleres i separate structer og metoder
- Kode strukturelt delt etter spesifikasjonen av nødvendig funksjonalitet

Til å begynne med kan Go være uvant fordi det skiller seg fra tradisjonell OOP, men når man blir vant til det, muliggjør det eksplisitt programmering.

## Sammendrag

- Isoler ansvarsområder
- Del opp detaljert på structnivå
- Metoder må ikke forstås som abstrakte klasser i Java
- Skriv eksplisitt og konkret kode
  Go er enklere og mer tydelig enn den tradisjonelle OOP-modellen og bør håndteres enkeltvis. Skriv heller trinnvis og strukturert, med separate deler, enn å programmere utvidbart.
