Go’da bir struct yalnızca veri taşımak zorunda değildir; ona metotlar bağlayarak belirli davranışlar kazandırabiliriz. Bu yaklaşım, klasik sınıf tabanlı nesne yönelimli programlamadan farklıdır: Go’da class, kalıtım veya this yoktur; bunun yerine sade, açık ve güçlü bir receiver mantığı vardır.
``
Go’da metot, teknik olarak bir fonksiyondur; fakat özel bir parametreye sahiptir: receiver. Receiver, metodun hangi tipe aitmiş gibi çağrılacağını belirler. Matematiksel olarak düşünürsek, normal bir fonksiyon şöyle modellenebilir: $f(x) \rightarrow y$. Metotta ise fonksiyon, belirli bir veri tipinin bağlamında çalışır: $T.f(x) \rightarrow y$. Buradaki $T$, örneğin bir User, Account veya Rectangle struct’ı olabilir.
Basit bir örnekle başlayalım:
package main
import "fmt"
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func main() {
box := Rectangle{Width: 10, Height: 5}
fmt.Println(box.Area())
}
Burada (r Rectangle) kısmı receiver’dır. Yani Area metodu, Rectangle tipine bağlanmıştır. Artık Area(box) yazmak yerine daha doğal bir şekilde box.Area() yazarız. Kodun okunabilirliği artar; çünkü davranış, ilgili veri yapısının yanında durur.
| Yaklaşım | Örnek | Okunabilirlik | Go’daki Yeri |
|---|---|---|---|
| Normal fonksiyon | Area(rect) |
Orta | Geçerli ve sık kullanılır |
| Metot | rect.Area() |
Yüksek | Struct davranışları için idealdir |
| Class metodu | rect.area() |
Dile bağlı | Go’da class yoktur |
Receiver iki şekilde tanımlanabilir: value receiver ve pointer receiver. Value receiver, struct’ın bir kopyası üzerinde çalışır. Pointer receiver ise orijinal veriye erişir ve onu değiştirebilir.
type Account struct {
Owner string
Balance float64
}
func (a Account) CurrentBalance() float64 {
return a.Balance
}
func (a *Account) Deposit(amount float64) {
if amount <= 0 {
return
}
a.Balance += amount
}
CurrentBalance yalnızca okuma yaptığı için value receiver yeterlidir. Deposit ise bakiyeyi değiştirdiği için pointer receiver kullanır. Eğer para yatırma işlemini formülle ifade edersek: $bakiye_{yeni} = bakiye_{eski} + miktar$. Bu işlemde eski değerin gerçekten güncellenmesi gerekir; kopya üzerinde çalışmak işe yaramaz.
| Receiver Türü | Tanım | Veriyi Değiştirir mi? | Ne Zaman Kullanılır? |
|---|---|---|---|
| Value receiver | (a Account) |
Hayır, kopya üzerinde çalışır | Küçük struct’lar, salt okuma işlemleri |
| Pointer receiver | (a *Account) |
Evet, orijinali değiştirebilir | Güncelleme, büyük struct, performans |
Go’nun burada sunduğu güzellik şudur: Nesne yönelimli programlamadaki “nesne davranış taşır” fikrini alır, ama sınıf hiyerarşisi karmaşasını dayatmaz. Bir struct veri modelini temsil eder, metotlar ise bu veri modeline ait davranışları ifade eder. Yani Go şunu der: “Veriyi ayrı düşün, davranışı açıkça bağla.”
Klasik OOP ile Go yaklaşımını karşılaştıralım:
| Kavram | Klasik OOP | Go Yaklaşımı |
|---|---|---|
| Sınıf | Temel yapı taşıdır | Yoktur |
| Nesne | Class örneğidir | Struct değeri olabilir |
| Metot | Class içinde tanımlanır | Herhangi bir tipe receiver ile bağlanır |
| Kalıtım | Sınıflar arası aktarım | Composition tercih edilir |
| Polymorphism | Inheritance veya interface | Interface ile örtük uygulanır |
Receiver adı genellikle kısa seçilir: u User, r Rectangle, a Account gibi. Go topluluğu this veya self benzeri isimler kullanmayı önermez. Çünkü receiver zaten bağlamı açıkça gösterir. Ayrıca aynı tipe ait metotlarda receiver adını tutarlı seçmek kodun ritmini güzelleştirir.
Biraz daha gerçekçi örnek düşünelim:
type Task struct {
Title string
Completed bool
}
func (t *Task) Complete() {
t.Completed = true
}
func (t Task) Status() string {
if t.Completed {
return "tamamlandı"
}
return "bekliyor"
}
Bu örnekte Task, sadece iki alanlı basit bir veri yapısıdır. Ancak Complete ve Status metotlarıyla birlikte artık küçük bir davranış modeline dönüşür. Kod başka bir yerde task.Complete() gördüğünde niyet hemen anlaşılır: görev tamamlanıyor.
Sonuç olarak Go’daki metot ve receiver sistemi, nesne yönelimli düşüncenin pratik tarafını korur: veriyle ilişkili davranışları bir araya getirir. Fakat bunu sınıf, kalıtım ve karmaşık hiyerarşiler olmadan yapar. Eğer bir struct’ın “ne olduğunu” alanları belirliyorsa, “ne yapabildiğini” de metotları belirler. Go’nun felsefesi de tam burada parlar: az sihir, çok netlik.
Yorumlar