Şimdiye kadar dilin hazır verdiği tiplerle çalıştın: sayılar, metinler, dilimler, haritalar. Gerçek programlar ise kendi kavramlarıyla konuşur: bir kullanıcı, bir sipariş, bir dikdörtgen, bir banka hesabı. Bu kavramları ifade etmenin Go'daki yolu struct'tır: Birbiriyle ilgili alanları tek bir isim altında toplayan bir veri tipi.
Struct'ları tanımladıktan sonra onlara davranış eklersin. Go'da bunun adı metottur: Belirli bir tiple ilişkilendirilmiş bir fonksiyon. Ama burada önemli bir fark vardır. Go'da sınıf yoktur, kalıtım yoktur, this yoktur. Bunun yerine sade bir fikir vardır: Bir fonksiyonun adından önce, o fonksiyonun hangi tipe ait olduğunu söyleyen bir alıcı (receiver) yazarsın. Bu küçük sözdizimsel fark, nesne yönelimli dillerin karmaşık miras hiyerarşilerine gerek kalmadan aynı işi görmeni sağlar.
Go'nun tercihi kalıtım yerine kompozisyondur: Bir tipi başka bir tipin içine koyarak yeteneklerini birleştirirsin. Bu derste struct tanımlamayı, literal yazımlarını, anonim struct'ları, metotları, değer ve işaretçi alıcılar arasındaki kritik farkı, gömmeyi (embedding), yapıcı fonksiyon desenini, struct karşılaştırmasını ve etiketleri (tags) öğreneceksin. Bu konular Go'da kendi kütüphanelerini yazmanın temelidir.
Struct tanımlama
Bir struct, alanların (field) sıralı bir listesidir. Her alanın bir adı ve bir tipi vardır:
package main
import "fmt"
type Book struct {
Title string
Author string
Year int
Pages int
}
func main() {
// 1) Alan adlarıyla (önerilen): sıra önemsiz, eksik alanlar sıfır değer alır
b1 := Book{
Title: "Tutunamayanlar",
Author: "Oğuz Atay",
Year: 1972,
}
// 2) Sırayla: tüm alanlar verilmeli, sıraya bağımlı — kırılgandır
b2 := Book{"Kürk Mantolu Madonna", "Sabahattin Ali", 1943, 160}
// 3) Boş struct: tüm alanlar sıfır değerde
var b3 Book
fmt.Printf("%+v\n", b1)
fmt.Printf("%+v\n", b2)
fmt.Printf("%+v\n", b3)
// Alanlara nokta ile erişilir
b1.Pages = 724
fmt.Println(b1.Title, "-", b1.Pages, "sayfa")
}{Title:Tutunamayanlar Author:Oğuz Atay Year:1972 Pages:0}
{Title:Kürk Mantolu Madonna Author:Sabahattin Ali Year:1943 Pages:160}
{Title: Author: Year:0 Pages:0}
Tutunamayanlar - 724 sayfaAlan adları da büyük/küçük harf kuralına tabidir: Büyük harfle başlayan alanlar paket dışından erişilebilir, küçük harfle başlayanlar erişilemez. Bu, bir tipin iç durumunu gizlemenin (encapsulation) Go'daki yoludur.
%+v fiili struct'ı alan adlarıyla birlikte yazdırır; hata ayıklarken en sık kullanacağın fiildir. %#v ise Go sözdizimiyle yazdırır, kopyalayıp koda yapıştırabileceğin biçimde.
İç içe ve anonim struct'lar
Bir struct'ın alanı başka bir struct olabilir. Bu, gerçek dünyadaki iç içe yapıları doğal biçimde ifade eder:
package main
import "fmt"
type Address struct {
City string
Country string
}
type Company struct {
Name string
Address Address // iç içe struct
Founded int
}
func main() {
c := Company{
Name: "Yıldız Yazılım",
Address: Address{City: "İzmir", Country: "Türkiye"},
Founded: 2015,
}
fmt.Println(c.Address.City)
// Anonim struct: tek kullanımlık, isim vermeye değmeyen yapılar
stats := struct {
Total int
Average float64
}{Total: 42, Average: 3.5}
fmt.Printf("%+v\n", stats)
// Tablo güdümlü testlerde çok kullanılan desen
cases := []struct {
input string
want int
}{
{"", 0},
{"go", 2},
{"merhaba", 7},
}
for _, tc := range cases {
got := len(tc.input)
fmt.Printf("%q → %d (beklenen %d, doğru mu: %t)\n", tc.input, got, tc.want, got == tc.want)
}
}İzmir
{Total:42 Average:3.5}
"" → 0 (beklenen 0, doğru mu: true)
"go" → 2 (beklenen 2, doğru mu: true)
"merhaba" → 7 (beklenen 7, doğru mu: true)Anonim struct'lar, yalnızca bir yerde kullanılacak geçici veri şekilleri için idealdir. En yaygın kullanım alanı tablo güdümlü testlerdir; bunu Test ve Benchmark dersinde göreceksin.
Metotlar
Metot, alıcısı olan bir fonksiyondur. Alıcı, func ile fonksiyon adının arasına parantez içinde yazılır:
package main
import (
"fmt"
"math"
)
type Rectangle struct {
Width float64
Height float64
}
// Değer alıcı: r, çağrıldığında kopyalanır
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// İşaretçi alıcı: r, gerçek nesneyi gösterir
func (r *Rectangle) Scale(factor float64) {
r.Width *= factor
r.Height *= factor
}
func (r Rectangle) Diagonal() float64 {
return math.Hypot(r.Width, r.Height)
}
func main() {
rect := Rectangle{Width: 3, Height: 4}
fmt.Printf("alan: %.1f, çevre: %.1f, köşegen: %.1f\n",
rect.Area(), rect.Perimeter(), rect.Diagonal())
rect.Scale(2) // Go otomatik olarak (&rect).Scale(2) yapar
fmt.Printf("ölçeklendikten sonra: %+v, alan: %.1f\n", rect, rect.Area())
}alan: 12.0, çevre: 14.0, köşegen: 5.0
ölçeklendikten sonra: {Width:6 Height:8}, alan: 48.0Metot yalnızca struct'lara değil, kendi paketinde tanımladığın her tipe eklenebilir. Bir sayıya, bir dilime veya bir fonksiyon tipine bile metot yazabilirsin:
package main
import (
"fmt"
"strings"
)
type Celsius float64
func (c Celsius) Fahrenheit() float64 { return float64(c)*9/5 + 32 }
func (c Celsius) String() string { return fmt.Sprintf("%.1f°C", float64(c)) }
type Tags []string
func (t Tags) Join() string { return strings.Join(t, ", ") }
func (t Tags) Has(name string) bool {
for _, tag := range t {
if tag == name {
return true
}
}
return false
}
func main() {
temp := Celsius(23.5)
fmt.Println(temp, "=", temp.Fahrenheit(), "°F")
tags := Tags{"go", "backend", "api"}
fmt.Println(tags.Join())
fmt.Println("api var mı:", tags.Has("api"), "| web var mı:", tags.Has("web"))
}23.5°C = 74.3 °F go, backend, api api var mı: true | web var mı: false
String() string metodu özeldir: fmt paketi bu metodu olan tipleri yazdırırken onu çağırır. Bu, fmt.Stringer arayüzünün gereğidir ve Arayüzler dersinin konusudur.
Değer alıcı mı, işaretçi alıcı mı?
Bu, Go öğrenirken en çok kafa karıştıran konudur; ama kural aslında kısadır.
Değer alıcı (func (r Rectangle)) metodu çağrıldığında struct kopyalanır. Metot içinde yaptığın değişiklikler orijinali etkilemez. Okuma yapan, yeni bir değer hesaplayan metotlar için uygundur.
İşaretçi alıcı (func (r *Rectangle)) metodu orijinal nesneyi gösterir. Değişiklikler kalıcıdır ve büyük struct'larda kopyalama maliyeti ortadan kalkar.
package main
import "fmt"
type Counter struct {
value int
name string
}
func (c Counter) IncrementByValue() { c.value++ } // kopyayı artırır: etkisiz
func (c *Counter) Increment() { c.value++ } // gerçek nesneyi artırır
func (c Counter) Value() int { return c.value }
func main() {
c := Counter{name: "istek"}
c.IncrementByValue()
c.IncrementByValue()
fmt.Println("değer alıcıdan sonra:", c.Value())
c.Increment()
c.Increment()
c.Increment()
fmt.Println("işaretçi alıcıdan sonra:", c.Value())
// Go, adres alma ve türetmeyi otomatik yapar
p := &c
fmt.Println("işaretçi üzerinden değer metodu:", p.Value()) // (*p).Value()
c.Increment() // (&c).Increment()
fmt.Println("son değer:", c.Value())
}değer alıcıdan sonra: 0 işaretçi alıcıdan sonra: 3 işaretçi üzerinden değer metodu: 3 son değer: 4
Pratikte kullanacağın kurallar:
- Metot alıcıyı değiştiriyorsa işaretçi alıcı kullan. Başka seçenek yoktur.
- Struct büyükse işaretçi alıcı kullan. Her çağrıda yüzlerce baytın kopyalanmasını önlersin.
- Tipin metotları karışık olmasın. Bir tipin bazı metotları işaretçi alıcıysa, hepsini işaretçi alıcı yap. Karışık kullanım hem kafa karıştırır hem de arayüz uyumunda sürprizlere yol açar.
- Küçük, değişmez (immutable) tiplerde değer alıcı kullan.
time.Time,Celsiusgibi tipler böyle tasarlanmıştır. - Alıcı
sync.Mutexgibi kopyalanmaması gereken bir alan içeriyorsa işaretçi alıcı zorunludur.
Gömme (embedding) ve kompozisyon
Bir struct'ın içine, alan adı yazmadan doğrudan bir tip koyarsan buna gömme denir. Gömülen tipin alanları ve metotları, dıştaki tipe yükseltilir (promoted): Sanki onun kendi üyeleriymiş gibi erişilir.
package main
import "fmt"
type Animal struct {
Name string
Legs int
}
func (a Animal) Describe() string {
return fmt.Sprintf("%s (%d bacak)", a.Name, a.Legs)
}
func (a Animal) Speak() string { return "..." }
type Dog struct {
Animal // gömülü: alan adı yok
Breed string
}
// Dog kendi Speak metodunu tanımlayarak gömülü olanı gölgeler
func (d Dog) Speak() string { return "Hav!" }
func main() {
d := Dog{
Animal: Animal{Name: "Karabaş", Legs: 4},
Breed: "Kangal",
}
fmt.Println(d.Name) // Animal'dan yükseltilen alan
fmt.Println(d.Describe()) // Animal'dan yükseltilen metot
fmt.Println(d.Speak()) // Dog'un kendi metodu
fmt.Println(d.Animal.Speak()) // gömülü olana açıkça erişim
fmt.Printf("%+v\n", d)
}Karabaş
Karabaş (4 bacak)
Hav!
...
{Animal:{Name:Karabaş Legs:4} Breed:Kangal}Gömme kalıtım değildir. Dog, Animal "değildir"; Dog, bir Animal içerir ve onun yeteneklerini devralmış gibi görünür. Aradaki fark önemlidir: Bir fonksiyon Animal parametresi bekliyorsa ona Dog geçiremezsin; d.Animal yazman gerekir. Gerçek çok biçimlilik (polymorphism) Go'da arayüzlerle sağlanır.
Gömmenin en sık kullanıldığı yer, bir tipin davranışını sararak genişletmektir. Standart kütüphanede bufio.ReadWriter tam olarak bunu yapar: içine *bufio.Reader ve *bufio.Writer gömerek her ikisinin metotlarını tek tipte toplar.
Yapıcı fonksiyon deseni
Go'da yapıcı (constructor) diye bir dil özelliği yoktur. Yerine, tipin geçerli bir örneğini döndüren bir fonksiyon yazılır. Gelenek, adının New ile başlamasıdır:
package main
import (
"errors"
"fmt"
"strings"
)
type Account struct {
owner string
balance int // kuruş cinsinden: ondalık hatası olmaz
}
// NewAccount geçerli bir hesap oluşturur; kuralları tek noktada uygular.
func NewAccount(owner string, initial int) (*Account, error) {
owner = strings.TrimSpace(owner)
if owner == "" {
return nil, errors.New("hesap sahibi boş olamaz")
}
if initial < 0 {
return nil, errors.New("başlangıç bakiyesi negatif olamaz")
}
return &Account{owner: owner, balance: initial}, nil
}
func (a *Account) Deposit(amount int) error {
if amount <= 0 {
return errors.New("yatırılan tutar pozitif olmalı")
}
a.balance += amount
return nil
}
func (a *Account) Withdraw(amount int) error {
if amount > a.balance {
return fmt.Errorf("yetersiz bakiye: %d kuruş istendi, %d kuruş var", amount, a.balance)
}
a.balance -= amount
return nil
}
func (a *Account) String() string {
return fmt.Sprintf("%s: %.2f TL", a.owner, float64(a.balance)/100)
}
func main() {
acc, err := NewAccount(" Ayşe ", 10_000)
if err != nil {
fmt.Println("hata:", err)
return
}
fmt.Println(acc)
_ = acc.Deposit(2_500)
fmt.Println("yatırma sonrası:", acc)
if err := acc.Withdraw(50_000); err != nil {
fmt.Println("çekim reddedildi:", err)
}
if _, err := NewAccount("", 100); err != nil {
fmt.Println("oluşturma reddedildi:", err)
}
}Ayşe: 100.00 TL yatırma sonrası: Ayşe: 125.00 TL çekim reddedildi: yetersiz bakiye: 50000 kuruş istendi, 12500 kuruş var oluşturma reddedildi: hesap sahibi boş olamaz
Alanların küçük harfle başlaması, paket dışından doğrudan değiştirilmelerini engeller. Böylece "bakiye asla negatif olamaz" gibi değişmezleri (invariant) yalnızca metotlar üzerinden garanti edebilirsin. Bu, Go'da kapsüllemenin tipik uygulanışıdır.
Karşılaştırma, kopyalama ve etiketler
İki struct, tüm alanları karşılaştırılabilir tiplerdeyse == ile karşılaştırılabilir. Karşılaştırma alan alan yapılır:
package main
import "fmt"
type Point struct{ X, Y int }
type Config struct {
Name string
Tags []string // dilim: karşılaştırılamaz
}
func main() {
p1 := Point{1, 2}
p2 := Point{1, 2}
fmt.Println("eşit mi:", p1 == p2)
// Karşılaştırılabilir struct'lar map anahtarı olabilir
grid := map[Point]string{
{0, 0}: "başlangıç",
{1, 2}: "hedef",
}
fmt.Println(grid[Point{1, 2}])
// Atama struct'ı KOPYALAR
p3 := p1
p3.X = 99
fmt.Println(p1, p3)
// Dilim içeren struct == ile karşılaştırılamaz (derleme hatası olurdu).
// Onun yerine reflect.DeepEqual veya alan alan karşılaştırma kullanılır.
c := Config{Name: "prod", Tags: []string{"a"}}
d := c // sığ kopya: Tags aynı diziyi gösterir
d.Tags[0] = "değişti"
fmt.Println(c.Tags[0], d.Tags[0])
}eşit mi: true
hedef
{1 2} {99 2}
değişti değiştiSon örnek sığ kopyalama (shallow copy) tuzağını gösterir: Struct kopyalandığında içindeki dilim başlığı da kopyalanır, ama altta yatan dizi paylaşılır. Derin kopya istiyorsan dilimi açıkça klonlaman gerekir.
Struct etiketleri
Alanların arkasına ters tırnak içinde yazılan metinlere etiket (tag) denir. Derleyici bunlara dokunmaz; çalışma zamanında yansıma (reflection) ile okunurlar. En yaygın kullanıcıları JSON, XML ve veritabanı kütüphaneleridir:
package main
import (
"encoding/json"
"fmt"
)
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
Discount float64 `json:"discount,omitempty"` // sıfırsa çıktıya konmaz
internal string // dışa açık değil: JSON'a hiç girmez
}
func main() {
p := Product{ID: 7, Name: "Klavye", Price: 899.90, internal: "gizli"}
data, err := json.Marshal(p)
if err != nil {
fmt.Println("hata:", err)
return
}
fmt.Println(string(data))
var decoded Product
if err := json.Unmarshal([]byte(`{"id":9,"name":"Fare","price":249.5,"discount":10}`), &decoded); err != nil {
fmt.Println("hata:", err)
return
}
fmt.Printf("%+v\n", decoded)
}{"id":7,"name":"Klavye","price":899.9}
{ID:9 Name:Fare Price:249.5 Discount:10 internal:}Etiketlerin ayrıntısını ve JSON dönüşümlerini Dosyalar, I/O ve JSON dersinde işleyeceğiz.
Struct tasarlarken düşünülecekler
Bir tipi tasarlamak, alanları yan yana dizmekten fazlasıdır. Aşağıdaki sorular, kod incelemelerinde en çok tartışılan konuları özetler.
Sıfır değeri kullanışlı mı? Go'nun en güzel alışkanlıklarından biri, bir tipin hiç kurulmadan, doğrudan tanımlanarak kullanılabilmesidir. Standart kütüphanede bunun pek çok örneği vardır: bir metin biriktirici, bir kilit ya da bir tampon, tanımlandığı anda hazırdır. Kendi tiplerini tasarlarken de aynı hedefi gözet. Sıfır değeri anlamsız oluyorsa, bunu bir yapıcı fonksiyonla telafi et ve belgede açıkça belirt.
Hangi bilgi dışarıya açık olmalı? Bir alanı dışa açtığında, onun her türlü değerinin geçerli olduğunu kabul etmiş olursun. Bir kural varsa — negatif olmayacak, boş kalmayacak, diğer alanla tutarlı olacak — o alanı gizle ve değişimi metotlar üzerinden yaptır. Aksi hâlde kuralı her çağrı yerinde yeniden kontrol etmen gerekir.
Tip ne kadar büyük? Küçük ve değişmez tipler değer olarak dolaşmaya uygundur; kopyalanmaları ucuzdur, eşzamanlı kullanımda sorun çıkarmazlar. Büyük tipler ya da içinde kilit, tampon gibi paylaşılması gereken şeyler barındıran tipler ise işaretçi olarak dolaştırılmalıdır. Bu karar, tipin metot alıcılarını da belirler.
Davranışı nereye koymalı? Yeni başlayanlar her şeyi metot yapma eğilimindedir. Oysa bir işlem tipin iç durumuna ihtiyaç duymuyorsa, onu sade bir fonksiyon olarak yazmak daha iyidir: Test etmesi kolay, bağımlılığı az olur. Metotları, gerçekten tipin durumuyla ilgilenen işlemlere ayır.
İsimler ne anlatıyor? Go'da kısa isimler makbuldür ama belirsiz isimler değil. Bir alanın adı, birimini ve anlamını taşımalıdır. Süre tutan bir alanın adında biriminin geçmesi, para tutan bir alanın kuruş mu lira mı olduğunu belli etmesi, ileride saatlerce süren hata avlarını baştan önler.
Bu soruların ortak noktası şudur: Tipi kullanan kişinin ne kadar az şey bilmesi gerekiyorsa, tasarım o kadar iyidir.
Sık yapılan hatalar
- Değer alıcılı metotla nesneyi değiştirmeye çalışmak. Kopya üzerinde çalışırsın; değişiklik kaybolur.
- Aynı tipte değer ve işaretçi alıcıları karıştırmak. Tutarlı ol; aksi hâlde arayüz uyumu şaşırtıcı biçimde bozulur.
- Gömmeyi kalıtım sanmak.
DogbirAnimaldeğildir; tip uyumluluğu için arayüz gerekir. - Sıraya dayalı struct literal'i kullanmak. Alan eklendiğinde kod sessizce bozulur.
- Sığ kopyanın dilim ve map alanlarını paylaştığını unutmak. Gerekirse
slices.Cloneveyamaps.Clonekullan. sync.Mutexiçeren bir struct'ı kopyalamak. Kilit de kopyalanır ve koruma anlamını yitirir.go vetbunu yakalar.- Etiketlerde tırnak yerine düz tırnak kullanmak. Etiketler ters tırnak (
`) ile yazılır vejson:"ad"biçiminde boşluksuz olmalıdır.
Alıştırmalar
Student adında bir struct tanımla: ad, numara ve notlar dilimi. Ortalamayı hesaplayan bir metot ve öğrenciyi okunur biçimde yazdıran bir String() metodu ekle. Üç öğrenci oluşturup yazdır.
İpucu
Ortalama hesaplarken boş not dilimini unutma; sıfıra bölme paniğe yol açar.
Çözümü göster
package main
import "fmt"
type Student struct {
Name string
Number int
Grades []int
}
func (s Student) Average() float64 {
if len(s.Grades) == 0 {
return 0
}
total := 0
for _, g := range s.Grades {
total += g
}
return float64(total) / float64(len(s.Grades))
}
func (s Student) String() string {
return fmt.Sprintf("%-8s (%d) ortalama: %.2f", s.Name, s.Number, s.Average())
}
func main() {
students := []Student{
{Name: "Ayşe", Number: 101, Grades: []int{90, 85, 78}},
{Name: "Mehmet", Number: 102, Grades: []int{60, 72}},
{Name: "Zeynep", Number: 103},
}
for _, s := range students {
fmt.Println(s)
}
}Ayşe (101) ortalama: 84.33 Mehmet (102) ortalama: 66.00 Zeynep (103) ortalama: 0.00
String() metodu değer alıcılıdır çünkü hiçbir şeyi değiştirmez ve struct küçüktür. fmt paketi bu metodu bulduğu için fmt.Println(s) doğrudan biçimli çıktıyı verir.
IntStack adında bir tip yaz: Push, Pop ve Peek metotları olsun. Pop ve Peek, yığın boşsa ikinci dönüş değeri olarak false versin. Hangi metotların işaretçi alıcı olması gerektiğine dikkat et.
İpucu
Push ve Pop yığını değiştirdiği için işaretçi alıcı olmalı; Peek ve Len değer alıcı olabilir ama tutarlılık için hepsini işaretçi yapmak da kabul edilir.
Çözümü göster
package main
import "fmt"
type IntStack struct {
items []int
}
func (s *IntStack) Push(v int) {
s.items = append(s.items, v)
}
func (s *IntStack) Pop() (int, bool) {
if len(s.items) == 0 {
return 0, false
}
last := len(s.items) - 1
v := s.items[last]
s.items = s.items[:last]
return v, true
}
func (s *IntStack) Peek() (int, bool) {
if len(s.items) == 0 {
return 0, false
}
return s.items[len(s.items)-1], true
}
func (s *IntStack) Len() int { return len(s.items) }
func main() {
var st IntStack // sıfır değer kullanıma hazır
for _, v := range []int{3, 7, 11} {
st.Push(v)
}
fmt.Println("uzunluk:", st.Len())
top, _ := st.Peek()
fmt.Println("tepe:", top)
for {
v, ok := st.Pop()
if !ok {
break
}
fmt.Print(v, " ")
}
fmt.Println()
_, ok := st.Pop()
fmt.Println("boş yığından çekilebildi mi:", ok)
}uzunluk: 3 tepe: 11 11 7 3 boş yığından çekilebildi mi: false
Push ve Pop iç dilimi değiştirdiği için işaretçi alıcı zorunludur: append yeni bir dilim başlığı üretir ve bunun struct'a geri yazılması gerekir. Yığın veri yapısını genel hâliyle Yığın (Stack) dersinde inceleyeceğiz.
Logger adında, ön ek tutan ve Log(mesaj string) metodu olan bir tip yaz. Ardından bu tipi gömen Service ve Worker tipleri tanımla. Service kendi Log metodunu tanımlayarak gömülü olanı gölgelesin ve mesaja fazladan bilgi eklesin. Çıktıda hem gölgeleyen hem gömülü metodun çağrılabildiğini göster.
İpucu
Gömülü metoda açıkça erişmek için s.Logger.Log(...) yazılır. Gölgeleyen metot içinde de bunu çağırabilirsin.
Çözümü göster
package main
import (
"fmt"
"strings"
)
type Logger struct {
Prefix string
lines []string
}
func (l *Logger) Log(msg string) {
l.lines = append(l.lines, fmt.Sprintf("[%s] %s", l.Prefix, msg))
}
func (l *Logger) Dump() string {
return strings.Join(l.lines, "\n")
}
type Worker struct {
Logger // gömülü: Log ve Dump doğrudan kullanılabilir
ID int
}
type Service struct {
Logger
Name string
}
// Service, gömülü Log metodunu gölgeler ve zenginleştirir.
func (s *Service) Log(msg string) {
s.Logger.Log(s.Name + ": " + msg)
}
func main() {
w := Worker{Logger: Logger{Prefix: "worker"}, ID: 3}
w.Log("iş alındı")
w.Log("iş tamamlandı")
s := Service{Logger: Logger{Prefix: "servis"}, Name: "ödeme"}
s.Log("istek geldi") // gölgeleyen metot
s.Logger.Log("ham mesaj") // gömülü metot
fmt.Println(w.Dump())
fmt.Println(s.Dump())
fmt.Println("worker ön eki:", w.Prefix, "| id:", w.ID)
}[worker] iş alındı [worker] iş tamamlandı [servis] ödeme: istek geldi [servis] ham mesaj worker ön eki: worker | id: 3
Gömme sayesinde Worker hiçbir kod yazmadan Log ve Dump yeteneklerini kazandı. Service ise aynı adı taşıyan kendi metodunu tanımlayarak dıştaki çağrıları yakaladı; gömülü davranışa s.Logger.Log ile erişmeye devam ediyor. Bu, kalıtımdaki "super çağrısı" fikrinin Go'daki karşılığıdır — ama aradaki ilişki "sahiptir" (has-a) ilişkisidir, "dır" (is-a) değil.
Kısa sınav
Değer alıcılı bir metot, alıcının alanını değiştirirse ne olur?
Go'da gömme (embedding) ile kalıtım arasındaki temel fark nedir?
Hangi struct == ile karşılaştırılabilir?
type Celsius float64 tipine metot eklenebilir mi?
Yapıcı fonksiyon deseni (NewX) neden kullanılır?
Bir struct'ı kopyaladığında dilim alanına ne olur?
Özet
- Struct, ilgili alanları tek tipte toplar; alan adlarıyla yazılan literal'ler değişikliğe dayanıklıdır.
- Alan ve metot adlarının ilk harfi görünürlüğü belirler; küçük harf kapsüllemenin Go'daki yoludur.
- Metot, alıcısı olan bir fonksiyondur ve kendi paketindeki her isimlendirilmiş tipe eklenebilir.
- Değer alıcı kopya üzerinde çalışır; işaretçi alıcı orijinali değiştirir ve büyük struct'larda kopyalamayı önler. Bir tipin alıcı türünü tutarlı tut.
- Gömme, alan ve metotları yükseltir; kompozisyon sağlar ama kalıtım değildir ve tip uyumluluğu doğurmaz.
New...yapıcı fonksiyonları geçerlilik kurallarını tek noktada toplar ve genelde(*T, error)döndürür.- Tüm alanları karşılaştırılabilir olan struct'lar
==ile karşılaştırılabilir ve map anahtarı olabilir. - Struct ataması sığ kopyadır; dilim ve map alanları paylaşılmaya devam eder.
- Etiketler derleyici için anlamsızdır; JSON gibi kütüphaneler bunları yansıma ile okur.