---
id: ebf46089667a6469b258b8e52e57571f
author: Yunjin Lee
title: 'Go Arayüzleri ve Yapı Gömme: Sınıf Kalıtımıyla Farklar ve Bileşim Tabanlı Tasarım'
description: Java/C++ sınıf kalıtımı ile Go arayüzlerinin yapısal tiplendirme (structural typing) farkını karşılaştırır ve struct gömme (struct embedding) ile metot devri (method delegation) kullanılarak uygulanan bileşim (Composition) desenini kod örnekleriyle açıklarız.
language: tr
date: 2025-07-17T10:40:02.284435348Z
path: /blog/posts/go-interfaces-are-not-inheritance-zd04c1084
category: language
---

# Genel Bakış

Go arayüzleri (interface), aynı argüman ve dönüş değerlerine sahip fonksiyonları birden fazla struct üzerinde kolayca bulundurmayı sağlar; ancak Java'daki extends anahtar kelimesinde olduğu gibi, iç fonksiyonların davranışını uygun şekilde genişletme ve geçersiz kılma (override) yöntemiyle aynı değildir. Go'nun bileşimsel (compositional) kod yeniden kullanımını doğru anlamadan kalıtımla karıştırmamak mümkün değildir; ancak başlangıçtan itibaren teorik olarak tam bir kavrayışa sahip olmak zordur. Hata yapmaya elverişli senaryolarla birlikte inceleyelim.

## Sık Yapılan Hatalar

Yeni başlayanlar aşağıdaki gibi hatalar yapabilir.

```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'nun geleneksel kalıtımı izlediğini düşündüğünüzde bu kod sorunsuz görünebilir. Ancak bu kodun çıktısı aşağıdaki gibidir.

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

Burada Go'nun davranışı yalnızca daha açık hale gelir.

```go
watermelon := apple
```

Bu kod, Apple'ı hiçbir şekilde Watermelon sınıfına dönüştürmez.
Sadece watermelon, apple'a işaret eden bir pointer'dır.

Bunu bir kez daha vurgulamak gerekirse, **Go geleneksel kalıtım kavramını izlemez.**

Bu yanlış anlama ile kod yazıldığında, anlamsız pointer oluşturma, beklenmeyen diğer struct'lar için fonksiyon kopyalama gibi ciddi hatalar ortaya çıkar.

Peki örnek teşkil eden kod nasıl olmalıdır?

## Go Dilinde Uygun Bir Örnek

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

Ancak Go'da da kalıtım **gibi** görünmesini sağlamak mümkündür.
Bu, anonim gömme (anonymous embedding) adı verilen bir örnektir. İç struct'ı isimsiz olarak tanımladığınızda bu mümkün olur.
Bu durumda **alt struct'ın alanlarına açıkça belirtmeden de** doğrudan erişilebilir.
Alt struct'ın alanlarını üst struct'a **yükseltme** (promotion) desenini kullanmak, bazı durumlarda okunabilirliği artırabilir. Ancak alt struct'ın açıkça gösterilmesi gereken durumlarda bu yöntemin kullanılmaması önerilir.

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

Bu örnekte şu fark vardır.

```go
w.PrintAll() // w.Friut.PrintAll() yerine, isimsiz struct üzerinden otomatik yükseltilmiş çağrı
```

Her iki örnek için de önemli noktalar şunlardır:

- main sade tutulur, fonksiyonlar işlevlerine göre ayrılır
- Farklı yapılar için farklı nesneler kullanılır
- Paylaşım gerektiğinde iç struct kullanılır

Bu programlama felsefesinin ne gibi faydaları vardır?

## Faydalar

- Paylaşılması gereken metotlar ile olmayanların ayrımı nettir
- Sorumluluk, ayrı struct ve metotlara ayrılır
- Gerekli işlev tanımına göre yapısal olarak ayrılmış kod

Başlangıçta Go dili geleneksel OOP'den farklı olduğu için alışık olunmayabilir; ancak alışıldığında açık ve net bir programlama yapmak mümkündür.

## Özet

- Sorumlulukları izole edelim
- Struct birimleri bazında ayrıntılı bölelim
- Metotlar Java'daki soyut sınıflar gibi anlaşılmamalıdır
- Açık ve somut bir programlama yapalım
  Go dili, geleneksel OOP modelinden daha sade ve net olup ayrı ayrı ele alınmalıdır. Genişletilebilir şekilde programlamak yerine, adım adım ve yapısal olarak ayrıştırarak yazalım.
