---
id: ebf46089667a6469b258b8e52e57571f
author: Yunjin Lee
title: 'Rozhrania a vnorenie štruktúr v jazyku Go: rozdiely oproti dedičnosti tried a návrh založený na kompozícii'
description: Porovnávame dedenie tried v jazykoch Java/C++ so štrukturálnym typovaním rozhraní v jazyku Go a vysvetľujeme kompozičný vzor (Composition) pomocou vnorenia štruktúr (struct embedding) a delegovania metód na konkrétfových príkladoch kódu.
language: sk
date: 2025-07-17T10:40:02.284435348Z
path: /blog/posts/go-interfaces-are-not-inheritance-zd04c1084
category: language
---

# Prehľad

Rozhrania v Go umožňujú jednoducho mať funkcie s rovnakými argumentmi a návratovými hodnotami vo viacerých štruktúrach, no tento prístup sa líši od spôsobu, akým kľúčové slovo extends v jazyku Java primerane rozširuje a prekrýva správanie vnútorných funkcií. Aby ste sa nezamieňali s dedičnosťou, musíte správne pochopiť kompozičné znovupoužitie kódu v Go, no od začiatku je teoreticky dokonalé pochopenie náročné. Pozrime sa na to spolu na scenároch, v ktorých sa ľahko robia chyby.

## Časté chyby

Začiatočníci môžu robiť nasledujúce chyby.

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

Ak si myslíte, že Go nasleduje tradičnú dedičnosť, tento kód sa zdá byť v poriadku. Jeho výstup je však nasledujúci.

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

Tu sa správanie Go stáva jasnejším.

```go
watermelon := apple
```

Tento kód vôbec nepremieňa Apple na triedu Watermelon.
Watermelon je len ukazovateľ na apple.

Ešte raz zdôrazňujem: **Go nenasleduje tradičný koncept dedičnosti.**

Ak píšete kód s týmto nesprávnym pochopením, vznikajú závažné chyby, ako sú zbytočné vytváranie ukazovateľov alebo neočakávané kopírovanie funkcií pre iné štruktúry.

Ako by teda vyzeral vzorový kód?

## Vhodný príklad v jazyku 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")
}
```

Napriek tomu je v Go možné urobiť, aby to **vyzeralo ako** dedičnosť.
Ide o takzvané anonymné vnorenie. Je to možné, keď vnútornú štruktúru deklarujete ako štruktúru bez mena.
V takomto prípade je možné pristupovať k poliam vnorenej štruktúry **aj bez ich explicitného uvedenia**.
Použitie vzoru, ktorý **povyšuje** polia vnorenej štruktúry do nadradenej štruktúry, môže v niektorých prípadoch zlepšiť čitateľnosť. Ak však potrebujete explicitne zobraziť vnorenú štruktúru, odporúčame tento prístup nepoužívať.

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

V tomto príklade je tento rozdiel:

```go
w.PrintAll() // Nie w.Friut.PrintAll(), ale automatické povýšenie volania cez anonymnú štruktúru
```

Pri oboch príkladoch sú dôležité tieto body:

- main nech je jednoduchý, funkcie podľa funkcionality
- pre iné štruktúry používajte iné objekty
- ak je potrebné zdieľanie, použite vnútornú štruktúru

Aké výhody prináša táto programovacia filozofia?

## Výhody

- Jasné rozlíšenie metód, ktoré vyžadujú zdieľanie, od tých, ktoré ho nevyžadujú
- Oddelenie zodpovednosti pre jednotlivé štruktúry a metódy
- Štrukturálne oddelený kód podľa špecifikácie požadovaných funkcií

Na začiatku môže byť jazyk Go neznámy, pretože sa líši od tradičného OOP, no keď si naň zvyknete, umožní explicitné programovanie.

## Zhrnutie

- Izolujte zodpovednosť
- Rozdeľujte podrobne na úrovni štruktúr
- Metódy nesmiete chápať ako abstraktné triedy v Jave
- Programujte explicitne a konkrétne
  Jazyk Go je jednoduchší a prehľadnejší než tradičný model OOP a mal by sa spracovávať samostatne. Namiesto rozširovania píšte kód postupne a štruktúrovane, s oddelením jednotlivých častí.
