---
id: ebf46089667a6469b258b8e52e57571f
author: Yunjin Lee
title: 'Go interfészek és struktúra-beágyazás: a klasszikus öröklődéstől való eltérések és a kompozíciós tervezés'
description: Összehasonlítjuk a Java/C++ osztályöröklődést és a Go interfészek strukturális típusozását, majd kódpéldákkal bemutatjuk a struktúra-beágyazáson és metódusdelegáláson alapuló kompozíciós (Composition) mintát.
language: hu
date: 2025-07-17T10:40:02.284435348Z
path: /blog/posts/go-interfaces-are-not-inheritance-zd04c1084
category: language
---

# Áttekintés

A Go interfészei megkönnyítik, hogy több struktúrának legyen azonos argumentumokkal és visszatérési értékekkel rendelkező függvénye, azonban ez eltér attól a módtól, ahogy a Java extends kulcsszava segítségével a belső függvények viselkedése megfelelően kiterjeszthető és felülírható. A Go kompozíción alapuló kódújrafelhasználását csak akkor értjük meg igazán, ha az nem keveredik az öröklődéssel, ugyanakkor a kezdetektől fogva elméletileg teljesen tökéletes megértés nehezen érhető el. Vizsgáljuk meg együtt olyan forgatókönyveken keresztül, amelyekben könnyen hibázhatunk.

## Gyakori hibák

A kezdők a következő hibákat követhetik el.

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

Ha valaki azt hiszi, hogy a Go hagyományos öröklődést követ, a fenti kód problémamentesnek tűnhet. Ennek kimenete azonban a következő:

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

Itt a Go működése csak ebben az esetben válik egyértelművé:

```go
watermelon := apple
```

Ez a kód egyáltalán nem alakítja át az Apple-t Watermelon osztállyá.
A watermelon egyszerűen csak az apple-re mutató pointer.

Ismételten hangsúlyozzuk: **a Go nem követi a hagyományos öröklődés fogalmát.**

Ha ezzel a félreértéssel írunk kódot, olyan súlyos hibákhoz juthatunk, mint a értelmetlen pointer-létrehozás vagy a váratlan módon más struktúrák számára történő függvénymásolás.

Akkor milyen a mintaszerű kód?

## Megfelelő példa Go nyelven

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

Go-ban azonban lehetséges, hogy az öröklődéshez **hasonló** módon járjunk el.
Ez az anonim beágyazás (embedding), amely úgy valósítható meg, hogy a belső struktúrát névtelen struktúraként deklaráljuk.
Ebben az esetben **az alstruktúra mezői külön megjelölés nélkül is** közvetlenül elérhetők.
Ha az alstruktúra mezőit a felső struktúrába **emeljük** (promóció), ez egyes esetekben javíthatja az olvashatóságot. Ha azonban az alstruktúrát explicit módon kell megjeleníteni, ezt a megoldást nem javasoljuk.

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

Ebben a példában a következő különbség figyelhető meg:

```go
w.PrintAll() // w.Friut.PrintAll() helyett, automatikus promóció a névtelen struktúrán keresztül
```

Mindkét példa esetében a következő pontok a lényegesek:

- a main legyen egyszerű, a függvények pedig funkció szerint legyenek szétválasztva
- különböző struktúrákhoz különböző objektumokat használjunk
- ha megosztásra van szükség, használjunk belső struktúrát

Milyen előnyei vannak ennek a programozási szemléletnek?

## Előnyök

- Egyértelmű különbségtétel a megosztott és nem megosztott metódusok között
- A felelősség szétválasztása az egyes struktúrák és metódusok között
- Szerkezetileg szétválasztott kód a szükséges funkcionális specifikáció szerint

Kezdetben a Go nyelv eltérése a hagyományos OOP-től szokatlanul hathat, de megszokva lehetővé teszi az explicit programozást.

## Összefoglalás

- Izoláljuk a felelősségi köröket
- Részletezzük a bontást struktúra szinten
- A metódusokat nem szabad a Java absztrakt osztályaihoz hasonlóan értelmezni
- Törekedjünk explicit és konkrét programozásra
  A Go nyelv egyszerűbb és közvetlenebb, mint a hagyományos OOP modell, és egyedileg kell kezelni. Ahelyett, hogy kiterjesztésekben gondolkodnánk, lépésről lépésre, strukturáltan szétválasztva írjunk kódot.
