---
id: ebf46089667a6469b258b8e52e57571f
author: Yunjin Lee
title: 'Go-rajapinnat ja rakenteiden upottaminen: erot luokkaperintään ja koostamiseen perustuvaan suunnitteluun'
description: Tässä vertaillaan Java/C++:n luokkaperintöä ja Go-kielen interfaceja sekä niiden structural typing -mallia, ja selitetään koodiesimerkkien avulla Composition-malli, joka hyödyntää struct embedding -tekniikkaa ja method delegation -menetelmää.
language: fi
date: 2025-07-17T10:40:02.284435348Z
path: /blog/posts/go-interfaces-are-not-inheritance-zd04c1084
category: language
---

# Yleiskatsaus

Go-rajapinnat mahdollistavat sen, että useilla structeilla on helposti funktioita, joilla on samat argumentit ja paluuarvot, mutta tämä eroaa Javan extends-avainsanan tavasta, jolla funktion sisäistä toimintaa laajennetaan ja ylikirjoitetaan asianmukaisesti. Go-kielen kompositiivisen koodin uudelleenkäytön ymmärtäminen on välttämätöntä, jotta sitä ei sekoiteta periytymiseen, mutta teoreettisesti täydellinen ymmärrys alusta alkaen on vaikeaa. Tutustutaan siihen virheherkkien skenaarioiden avulla.

## Yleisiä virheitä

Aloittelijat saattavat tehdä seuraavanlaisia virheitä.

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

Jos ajattelee, että Go noudattaa perinteistä periytymistä, tämä koodi näyttää ongelmattomalta. Sen tulostus on kuitenkin seuraava.

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

Tässä Go-kielen toiminta selkeytyy vain vähän:

```go
watermelon := apple
```

Tämä koodi ei lainkaan muunna Applea sellaisenaan Watermelon-luokaksi.
Watermelon on pelkästään osoitin apple-muuttujaan.

Tässä on vielä kerran korostettava: **Go ei noudata perinteistä periytymiskäsitettä.**

Jos kirjoittaa koodia tällaisen väärinkäsityksen vallitessa, seurauksena on merkityksettömiä osoittimia, odottamatonta funktioiden kopiointia muille structeille ja muita vakavia virheitä.

Millainen on sitten mallikelpoinen koodi?

## Sopiva esimerkki Go-kielellä

```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-kielessä on kuitenkin mahdollista saada aikaan periytymistä **muistuttava** rakenne.
Kyseessä on anonyymi upotus. Se toteutuu, kun sisempi struct määritellään nimettömänä structina.
Tässä tapauksessa **alemman structin kenttiin voi päästä käsiksi ilman erillistä määritystä** suoraan.
Kun käytetään tätä alemman structin kenttien nostamista ylemmälle tasolle (promotion), luettavuus voi joissakin tapauksissa parantua. Jos alempi struct on kuitenkin tarpeen näyttää eksplisiittisesti, suosittelemme, ettei tätä menetelmää käytetä.

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

Tässä esimerkissä on seuraava ero.

```go
w.PrintAll() // w.Friut.PrintAll()이 아닌, 이름 없는 구조체를 통한 자동 승격 호출
```

Molemmissa esimerkeissä tärkeät kohdat ovat seuraavat:

- main pidetään yksinkertaisena, funktiot jaotellaan toiminnoittain
- eri structeille käytetään eri olioita
- kun jaettavuutta tarvitaan, käytetään sisempää structia

Mitä etua tällaisesta ohjelmointifilosofiasta on?

## Edut

- Selkeä erottelu metodien välillä, jotka tarvitsevat jaettavuutta, ja niiden, jotka eivät tarvitse
- Vastuualueiden erottaminen yksittäisten structien ja metodien välillä
- Koodi, joka on rakenteellisesti jaettu tarvittavan toiminnallisuuden määrittelyn mukaan

Aluksi Go-kieli, joka poikkeaa perinteisestä OOP:stä, saattaa tuntua vieraalta, mutta totuttuaan siihen voi kirjoittaa eksplisiittistä ohjelmointia.

## Yhteenveto

- Eristä vastuualueet
- Jaa koodi yksittäisten structien tasolla
- Metodeja ei tule ymmärtää kuten Javan abstrakteja luokkia
- Kirjoita eksplisiittistä ja konkreettista ohjelmointia
  Go-kieli on yksinkertaisempi ja selkeämpi kuin perinteinen OOP-malli, ja sen osat tulee käsitellä erikseen. Sen sijaan, että ohjelmoisi laajentavasti, kirjoita koodi vaiheittain ja rakenteellisesti erotellen.
