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.
Görev
KolayGo'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 birNotify(message string) stringmetoduna sahip birNotifierarayüzü tanımlayın. Ayrıca, diğer türler tarafından gömülecek birNamealanına sahip birBaseNotifieryapısı oluşturun.BaseNotifieryapısına, mesajın başına köşeli parantez içinde bildirimcinin adını ekleyerek döndürenFormat(message string) stringadlı bir metod verin.channels.go:BaseNotifieryapısını gömen veNotifierarayüzünü uygulayan iki bildirim kanalı türü oluşturun:- Ek bir
Addressalanına sahipEmailNotifier - Ek bir
Phonealanına sahipSMSNotifier
Notifymetodu, gömülüFormatmetodunu kullanmalı ve çıktıya kendi kanalına özgü bilgileri dahil etmelidir.- Ek bir
main.go: Herhangi birNotifierve bir mesaj kabul eden, ardındanNotifyçağırma sonucunu döndürenSendAlertadlı 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] messageNotify 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 downHer 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
}
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
1Go OOP Temelleri
Harici DosyalarGo Çalışma Alanı ve ModüllerPaketler ve İçe AktarmalarDışa Aktarılan ve Aktarılmayan İsimlerGo'da OOP'ye GirişSınıf Olarak Struct'larStruct'larda Metot TanımlamaPointer ve Değer AlıcılarStruct BaşlatmaYapıcı FonksiyonlarÖzet - Basit Hesap Makinesi4Arayüzler
Arayüzlere GirişÖrtük UygulamaSözleşme Olarak ArayüzBoş Arayüz (any)Tür OnaylamaTür SeçimiArayüz BileşimiStringer ve Error ArayüzleriÖzet - Şekil Hesaplayıcı7Kapsülleme
Dışa Aktarılan ve Aktarılmayan AlanlarPaket Seviyesinde KapsüllemeGetter ve Setter MetotlarıGo'da Bilgi GizlemeÖzet - Öğrenci Kayıtları10Generics (Go 1.18+)
Generics'e GirişTür ParametreleriTür KısıtlamalarıGeneric Struct'larGeneric Metotlar İçin Geçici ÇözümÖzet - Generic Koleksiyon13Tasarım Kalıpları Bölüm 1
Tasarım Kalıplarına GirişSingleton KalıbıFactory KalıbıAbstract Factory KalıbıObserver KalıbıStrategy Kalıbı2Türler ve Struct'lar: Derinlemesine İnceleme
Temel ve Bileşik TürlerÖzel Tür TanımlamalarıStruct EtiketleriAnonim Struct'larİç İçe Geçmiş Struct'larSıfır Değerler ve VarsayılanlarÖzet - Rehber5Kalıtım Yerine Kompozisyon
Go'da Neden Kalıtım YokStruct Gömme TemelleriMetot YükseltmeBirden Fazla Struct GömmeGömme vs AgregasyonGömülü Metotları GölgelemeÖzet - Çalışan Hiyerarşisi8Hata Yönetimi ve OOP
error ArayüzüÖzel Hata TürleriHata Sarmalama (fmt.Errorf)Sentinel Hatalarerrors.Is() ve errors.As()Panic, Defer ve RecoverÖzet - Dosya Ayrıştırıcı3İşaretçiler ve Bellek
Go'da İşaretçi TemelleriStruct İşaretçileriDeğer ile Geçme ve Referans ile Geçmenew() FonksiyonuGo'da Garbage CollectionÖzet - Linked List Oluşturucu6Go'da Polimorfizm
Interface'ler ile PolimorfizmGo'da Duck TypingInterface Karşılama KurallarıPolimorfik KoleksiyonlarDependency InjectionÖzet - Payment Processor9Eşzamanlılık ve OOP
Goroutine TemelleriKanallar ve İletişimTamponlu ve Tamponsuz KanallarSelect İfadesisync.Mutex ve sync.RWMutexsync.WaitGroupThread-Safe Struct TasarımıÖzet - Worker Pool