---
id: ebf46089667a6469b258b8e52e57571f
author: Yunjin Lee
title: 'Interfejsy i osadzanie struktur w Go: różnice względem dziedziczenia klas oraz projektowanie oparte na kompozycji'
description: Porównuje różnice między dziedziczeniem klas w Javie/C++ a strukturalnym typowaniem interfejsów w Go oraz wyjaśnia, na przykładach kodu, wzorzec kompozycji (Composition) wykorzystujący osadzanie struktur (struct embedding) i delegowanie metod.
language: pl
date: 2025-07-17T10:40:02.284435348Z
path: /blog/posts/go-interfaces-are-not-inheritance-zd04c1084
category: language
---

# Przegląd

Interfejsy w Go umożliwiają łatwe posiadanie przez wiele struktur funkcji o tych samych argumentach i typach zwracanych, jednak różnią się one od sposobu, w jaki słowo kluczowe extends w Javie odpowiednio rozszerza i nadpisuje zachowanie tych funkcji. Aby nie pomylić kompozycyjnego ponownego wykorzystania kodu w Go z dziedziczeniem, trzeba je prawidłowo zrozumieć, jednak od razu osiągnięcie w pełni teoretycznego zrozumienia jest trudne. Przyjrzyjmy się temu na scenariuszach, w których łatwo popełnić błąd.

## Typowe błędy

Początkujący mogą popełniać następujące błędy.

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

Ktoś, kto uważa, że Go stosuje tradycyjne dziedziczenie, może sądzić, że powyższy kod nie sprawia problemów. Jednak jego wynik wygląda następująco.

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

Tutaj działanie Go staje się po prostu jasne.

```go
watermelon := apple
```

Ten kod wcale nie przekształca Apple w klasę Watermelon.
Zmienna watermelon jest jedynie wskaźnikiem na apple.

Podkreślmy to ponownie: **Go nie stosuje tradycyjnej koncepcji dziedziczenia.**

Pisanie kodu z takim nieporozumieniem prowadzi do poważnych błędów, takich jak bezsensowne tworzenie wskaźników czy nieoczekiwane kopiowanie funkcji dla innych struktur.

Jak więc wygląda wzorcowy kod?

## Odpowiedni przykład w języku 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")
}
```

Jednak w Go można też sprawić, by wyglądało to **jak** dziedziczenie.
Jest to przykład osadzania anonimowego. Jeśli wewnętrzną strukturę zadeklarujemy jako strukturę bez nazwy, możliwe jest to właśnie w ten sposób.
W takim przypadku można uzyskiwać dostęp do pól struktury podrzędnej **nawet bez jawnego odwoływania się do niej**.
Wzorzec, w którym pola struktury podrzędnej są **awansowane** do struktury nadrzędnej, może w niektórych przypadkach poprawić czytelność. Jednak w sytuacjach, gdy struktura podrzędna powinna być jawnie widoczna, zalecamy jego niestosowanie.

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

W tym przykładzie widoczna jest następująca różnica.

```go
w.PrintAll() // Wywołanie automatycznie awansowane przez strukturę bez nazwy, a nie w.Fruit.PrintAll()
```

Oba przykłady wskazują na następujące istotne kwestie:

- main powinien być prosty, a funkcje podzielone według funkcjonalności
- Różne struktury powinny reprezentować różne obiekty
- Gdy potrzebne jest współdzielenie, należy użyć struktury wewnętrznej

Jakie korzyści płyną z takiej filozofii programowania?

## Zalety

- Wyraźne rozróżnienie metod, które wymagają współdzielenia, od tych, które go nie wymagają
- Rozdzielenie odpowiedzialności między poszczególne struktury i metody
- Kod strukturalnie podzielony zgodnie z wymaganą specyfikacją funkcji

Na początku język Go, różniący się od tradycyjnego OOP, może wydawać się nieznajomy, jednak po oswojeniu się z nim umożliwia pisanie programów w sposób jawny.

## Podsumowanie

- Izolujmy odpowiedzialność
- Dzielmy kod szczegółowo na poziomie struktur
- Metod nie należy rozumieć jak klas abstrakcyjnych z Javy
- Piszmy jawny i konkretny kod
  Język Go powinien być traktowany jako prostszy i bardziej przejrzysty niż tradycyjny model OOP, a poszczególne elementy należy obsługiwać osobno. Zamiast programować w sposób rozszerzalny, piszmy kod etapami i w sposób strukturalnie rozdzielony.
