---
id: ebf46089667a6469b258b8e52e57571f
author: Yunjin Lee
title: 'Интерфейси и вграждане на структури в Go: разлики с класовото наследяване и проектиране чрез композиция'
description: Сравнява се разликата между наследяването на класове в Java/C++ и структурното типизиране чрез интерфейсите в Go, а също така се обяснява шаблонът за композиция (Composition), реализиран чрез вграждане на структури (struct embedding) и делегиране на методи, с примери на код.
language: bg
date: 2025-07-17T10:40:02.284435348Z
path: /blog/posts/go-interfaces-are-not-inheritance-zd04c1084
category: language
---

# Общ преглед

Интерфейсите в Go позволяват структури с различни типове да имат функции със същите аргументи и връщани стойности по лесен начин, но това е различно от начина, по който ключовата дума extends в java разширява и предефинира поведението на вътрешните функции. За да не се бърка с наследяване, е необходимо добре да се разбере композицията за повторно използване на код в Go, но е трудно да се постигне напълно теоретично пълно разбиране от самото начало. Нека разгледаме това със сценарии, в които е лесно да се допуснат грешки.

## Често срещани грешки

Начинаещите могат да допуснат следните грешки.

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

Ако мислите, че Go следва традиционното наследяване, този код изглежда без проблеми. Но изходът му е следният:

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

Тук поведението на Go става ясно.

```go
watermelon := apple
```

Този код изобщо не превръща Apple в класа Watermelon.
Просто watermelon е указател към apple.

Още веднъж подчертаваме: **Go не следва традиционната концепция за наследяване.**

Ако кодът се пише с това погрешно разбиране, възникват сериозни грешки като безсмислено създаване на указатели и копиране на функции към непредвидени други структури.

Как тогава би изглеждал примерният код?

## Подходящ пример в 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")
}
```

Въпреки това, в 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
	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")
}
```

В този пример има следната разлика.

```go
w.PrintAll() // Автоматично издигане и извикване през безименната структура, а не w.Fruit.PrintAll()
```

И двата примера имат следните важни моменти:

- main е кратък, функциите са разделени по функционалност
- различните структури използват различни обекти
- при необходимост от споделяне се използва вътрешна структура

Какви са предимствата на тази програмна философия?

## Предимства

- Ясно разграничение между методите, които трябва да се споделят, и тези, които не
- Разделяне на отговорностите между отделните структури и методи
- Структурно разделен код според спецификацията на необходимите функции

В началото езикът Go може да изглежда непознат, тъй като се различава от традиционното ООП, но с течение на времето е възможно да се пише изрично програмиране.

## Обобщение

- Изолирайте отговорностите
- Разделяйте по структури в детайли
- Методите не трябва да се разбират като абстрактни класове в Java
- Пишете изрично и конкретно
  Езикът Go трябва да се разглежда по-просто и ясно от традиционния OOP модел и с отделни елементи. Вместо да програмираме разширяемо, нека пишем на етапи и структурно разделено.
