Menu
Coddy logo textTech

Go'da Neden Kalıtım Yok

Coddy'nin GO Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 34 / 107.

Java ve C++ gibi geleneksel nesne yönelimli diller, türler arasında kod paylaşmak için kalıtım kullanır. Bir alt sınıf, bir üst sınıfı genişleterek onun tüm alanlarını ve metotlarını miras alır. Go, bu özelliği bilinçli olarak dışarıda bırakır.

Kalıtım, türler arasında sıkı bağlılık oluşturur. Bir ebeveyn sınıf değiştiğinde, tüm alt sınıflar etkilenir.

Derin kalıtım hiyerarşilerinin anlaşılması ve sürdürülmesi zorlaşır. "Kırılgan temel sınıf sorunu" (fragile base class problem), bir temel sınıfta yapılan değişikliklerin beklenmedik bir şekilde türetilmiş sınıfları bozmasıyla ortaya çıkar.

Go'nun tasarımcıları farklı bir yol seçti: kalıtım yerine kompozisyon. "Bir Köpek bir Hayvandır" demek yerine, Go sizi "bir Köpek Hayvan benzeri davranışlara sahiptir" demeye teşvik eder. Bu ince değişim, daha esnek ve bakımı kolay kodlara yol açar.

Go, halihazırda öğrendiğiniz iki mekanizma aracılığıyla kodun yeniden kullanımını sağlar:

  • Arayüzler, uygulama detayları içermeden davranış sözleşmelerini tanımlar
  • Struct embedding, türlerin diğer türleri içermesine ve metotlarını yeniden kullanmasına olanak tanır

Bu karşılaştırmayı düşünün. Geleneksel OOP'de, class Dog extends Animal yazabilirsiniz. Go'da, Dog içine bir Animal struct'ı gömer ve paylaşılan arayüzleri (interface) uygularsınız. Sonuç, türler arasında daha gevşek bir bağlılık ile benzer bir işlevselliktir.

Bu bölüm, struct gömmeyi (struct embedding) derinlemesine inceleyerek Go'nun kalıtımın (inheritance) avantajlarını dezavantajları olmadan nasıl sunduğunu gösterir.

challenge icon

Görev

Kolay

Go'nin kalıtım yerine kompozisyon yaklaşımını gösteren bir bildirim sistemi oluşturalım. Sınıf hiyerarşileri yerine arayüzler (interfaces) ve yapı gömme (struct embedding) yoluyla davranışı paylaşan türler oluşturacaksınız.

Kodunuzu üç dosya halinde düzenleyeceksiniz:

  • notifier.go: Tek bir Notify(message string) string metoduna sahip bir Notifier arayüzü tanımlayın. Ayrıca, diğer türler tarafından gömülecek bir Name alanına sahip bir BaseNotifier yapısı oluşturun. BaseNotifier yapısına, mesajın başına köşeli parantez içinde bildirimcinin adını ekleyerek döndüren Format(message string) string adlı bir metod verin.
  • channels.go: BaseNotifier yapısını gömen ve Notifier arayüzünü uygulayan iki bildirim kanalı türü oluşturun:
    • Ek bir Address alanına sahip EmailNotifier
    • Ek bir Phone alanına sahip SMSNotifier
    Her türün Notify metodu, gömülü Format metodunu kullanmalı ve çıktıya kendi kanalına özgü bilgileri dahil etmelidir.
  • main.go: Herhangi bir Notifier ve bir mesaj kabul eden, ardından Notify çağırma sonucunu döndüren SendAlert adlı bir fonksiyon oluşturun. Bildirim ayrıntılarını girdiden okuyun, her iki bildirimci türünü de oluşturun ve arayüz aracılığıyla birbirlerinin yerine nasıl kullanılabileceklerini gösterin.

Aşağıdaki girdiler sağlanacaktır:

  • Satır 1: E-posta için bildirimci adı
  • Satır 2: E-posta adresi
  • Satır 3: SMS için bildirimci adı
  • Satır 4: Telefon numarası
  • Satır 5: Uyarı mesajı

BaseNotifier üzerindeki Format metodunuz şunu döndürmelidir:

[Name] message

Notify metodlarınız şunları döndürmelidir:

  • EmailNotifier: Email to [Address]: [formatted message]
  • SMSNotifier: SMS to [Phone]: [formatted message]

Örneğin, Alerts, user@mail.com, Urgent, 555-1234 ve Server down verildiğinde, çıktınız şöyle olmalıdır:

Email to user@mail.com: [Alerts] Server down
SMS to 555-1234: [Urgent] Server down

Her iki bildirimci türünün de gömme yoluyla BaseNotifier'dan Format metodunu nasıl yeniden kullandığına, her birinin ise kendi Notify uygulamasını nasıl sağladığına dikkat edin. SendAlert fonksiyonu, somut türü bilmeden herhangi bir Notifier ile çalışır; bu, kalıtım yerine kompozisyonun (composition over inheritance) iş başındaki halidir.

Kopya kağıdı

Go, türler arasında kod paylaşmak için kalıtım yerine kompozisyonu (composition over inheritance) kullanır; böylece geleneksel nesne yönelimli kalıtımın sıkı bağlılık ve kırılgan temel sınıf problemlerinden kaçınır.

Sınıf hiyerarşileri yerine Go, kodun yeniden kullanımını şunlar aracılığıyla sağlar:

  • Arayüzler (Interfaces) - uygulama detayları olmadan davranış sözleşmeleri tanımlar
  • Struct gömme (Struct embedding) - türlerin diğer türleri içermesine ve onların metotlarını yeniden kullanmasına olanak tanır

"Bir Köpek bir Hayvandır" (kalıtım) demek yerine Go, "bir Köpek Hayvan benzeri davranışlara sahiptir" (kompozisyon) yaklaşımını teşvik eder.

Struct Gömme Örneği

Gömülecek olan temel bir struct tanımlayın:

type BaseNotifier struct {
    Name string
}

func (b BaseNotifier) Format(message string) string {
    return "[" + b.Name + "] " + message
}

Alanlarını ve metotlarını yeniden kullanmak için temel struct'ı diğer türlerin içine gömün:

type EmailNotifier struct {
    BaseNotifier  // gömülü struct
    Address string
}

func (e EmailNotifier) Notify(message string) string {
    // Gömülü Format metodu doğrudan çağrılabilir
    return "Email to " + e.Address + ": " + e.Format(message)
}

Birden fazla türün uygulayabileceği bir arayüz (interface) tanımlayın:

type Notifier interface {
    Notify(message string) string
}

Farklı somut türlerle birbirinin yerine çalışabilmek için arayüzü kullanın:

func SendAlert(n Notifier, message string) string {
    return n.Notify(message)
}

Bu yaklaşım, kalıtıma benzer bir işlevsellik sağlar ancak türler arasında daha gevşek bir bağlılık sunar.

Kendin dene

package main

import (
	"bufio"
	"fmt"
	"os"
)

// SendAlert herhangi bir Notifier ve bir mesaj kabul eder, Notify çağrısının sonucunu döndürür
// TODO: SendAlert fonksiyonunu uygula

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// E-posta bildirimci adını oku
	scanner.Scan()
	emailName := scanner.Text()
	
	// E-posta adresini oku
	scanner.Scan()
	emailAddress := scanner.Text()
	
	// SMS bildirimci adını oku
	scanner.Scan()
	smsName := scanner.Text()
	
	// Telefon numarasını oku
	scanner.Scan()
	phoneNumber := scanner.Text()
	
	// Uyarı mesajını oku
	scanner.Scan()
	alertMessage := scanner.Text()
	
	// TODO: emailName ve emailAddress ile bir EmailNotifier oluştur
	
	// TODO: smsName ve phoneNumber ile bir SMSNotifier oluştur
	
	// TODO: alertMessage mesajını her iki bildirimci aracılığıyla göndermek için SendAlert kullan
	// ve sonuçları yazdır
	
	// Kullanılmayan değişken uyarılarını bastır (değişkenleri kullandığında bunları kaldır)
	_ = emailName
	_ = emailAddress
	_ = smsName
	_ = phoneNumber
	_ = alertMessage
}
quiz iconKendini test et

Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.

Nesne Yönelimli Programlama bölümündeki tüm dersler