Kalıtımla (inheritance) bir sınıf, başka bir sınıfın metotlarını yeniden yazmadan devralır ve gerekenleri kendine göre değiştirir. Örneğin Developer sınıfı, Employee sınıfındaki describe() metodunu hazır bulur. Yanlış kullanıldığında ise sınıflar birbirine sıkı bağlanır ve sınıf hiyerarşisi (üst ve alt sınıfların oluşturduğu ağaç) kırılganlaşır: üst sınıftaki küçük bir değişiklik alt sınıfları bozabilir. Bu hafta kalıtımı is-a (“bir … türüdür”) ilişkisinden yola çıkarak öğrenecek ve geçen haftaki kompozisyonla karşılaştıracağız.
Bu bölümün kapsamı
NoteSınavda sorulur
Alt sınıfın üst sınıftan davranış devralması ve tür ilişkisi
Kalıtım kararını yerine geçme sorusuyla sınama
Bir metodu override ederek davranışı özelleştirme
super() çağrısının normal metotta da kullanılması
is-a ile has-a ayrımı; ortak alanın kalıtım gerekçesi olmaması
Derin kalıtım zincirinin kırılganlığı
Override edilen ve devralınan metotların çağrıda hangi sınıftan geldiğini izleme
NoteSınav dışı (bilgi amaçlı)
Çoklu kalıtım ve MRO terimleri
Çoklu kalıtım tasarımı ve mixin kalıpları
MRO algoritmasının (C3) ayrıntısı
Soyut temel sınıf (ABC) yazımı
Bu başlıklar konunun devamıdır; ileride karşınıza çıkar ama bu derste ezberlemeniz beklenmiyor.
Bu iki sınıf arasındaki ilişki is-a mı, has-a mı?
“Kare bir dikdörtgendir” cümlesi matematikte doğrudur. Yine de Square sınıfını Rectangle sınıfından türetmek programda sorun çıkarabilir. Bu bölümde “Bir X, bir Y’dir” cümlesinin ne zaman kalıtıma yol gösterdiğini, ne zaman yanılttığını göreceğiz.
İlk kalıtım örneği
Developer sınıfının gövdesinde hiçbir metot yok, ama describe() metodunu Employee sınıfından devralır. Bir Developer nesnesi aynı zamanda bir Employee sayılır; bu nedenle iki isinstance() çağrısı da True döndürür.
is-a yeterli mi? Liskov yerine geçme ilkesi
Kalıtım için ilk sezgi:
Bir Developer, bir Employee’dir.
Ama daha belirleyici soru şu:
Bir Employee beklenen yerde Developer kullanıldığında program, üst sınıfın sözleşmesi bozulmadan çalışıyor mu?
Yazılım tasarımında bu soru Liskov yerine geçme ilkesi (Liskov Substitution Principle, LSP) adıyla bilinir. İlkenin biçimsel tanımını ezberlemeyeceğiz; alt türün, üst türden beklenen davranışı bozup bozmadığına bakacağız. Örneğin çalışanları listeleyen bir fonksiyon her nesnenin describe() metodunu çağırıyorsa, listeye bir Developer koyduğunuzda bu çağrı yine bir metin döndürmelidir.
Override: davranışı özelleştirmek
Alt sınıftaki send, üst sınıftaki aynı adlı metodu ezer (override): bir EmailNotification nesnesinde send çağrılınca alt sınıftaki sürüm çalışır. Override ederken çağıranın beklentisini bozmayın. Burada iki sürüm de aynı parametreyi alıyor ve bir metin döndürüyor.
Kalıtım ve override
super() yalnızca __init__ için değildir
Önce kurucudaki kullanımına bakalım:
super().__init__(name) üst sınıfın __init__ metodunu çalıştırır. Böylece self.name = name satırını alt sınıfta yeniden yazmanız gerekmez.
super() çağrı akışı
super() normal metotlarda da çalışır. Üst sınıfın sonucunu alıp üstüne bir şey eklemeye davranışı genişletmek denir:
Alt sınıf, üst sınıfın send metodunu silip baştan yazmaz. Onu çağırır ve dönen metnin sonuna [kanal=e-posta] ekler.
is-a ve has-a
İlişki
Örnek
Tipik araç
is-a
Developer bir Employee’dir
kalıtım
has-a
Car bir Engine’e sahiptir
kompozisyon
İki sınıfta aynı adlı bir nitelik ya da metot bulunması, birini ötekinden türetmek için gerekçe olmaz. Driver ve Car sınıflarının ikisinde de name bulunabilir; ama “sürücü bir arabadır” (Driver is a Car) cümlesi anlamsızdır.
Tanımanız yeterli: kırılgan hiyerarşi ve MRO
Kalıtım zinciri derinleştikçe, yani B sınıfı A’dan, C sınıfı B’den, D sınıfı C’den türetildikçe, A sınıfındaki küçük bir değişiklik bütün alt sınıflara yayılır. Bu derste iki-üç seviyeden derin hiyerarşi tasarlamayacağız.
Python bir sınıfın birden fazla üst sınıftan türetilmesine de izin verir. Buna çoklu kalıtım denir:
class C(A, B):pass
A ve B sınıflarında aynı adlı bir metot varsa Python önce hangisine bakar? Bu arama sırasına method resolution order (MRO) denir. Sırayı C.mro() ile görebilirsiniz. Çoklu kalıtım ve MRO alıştırmalarda ve sınavda sorulmaz. İnternette bu terimlerle karşılaştığınızda ne olduklarını bilmeniz yeter.
WarningKalıtım öğrendik diye her yerde kullanmayın
Bir tasarımda üst ve alt sınıf sayısı arttıkça tasarım iyileşmez. Kompozisyon çoğu durumda daha gevşek ve değiştirilebilir bir bağ kurar: Car bir Engine nesnesi tutuyorsa, motoru aynı metotlara sahip başka bir motor nesnesiyle değiştirmek için Car sınıfına dokunmanız gerekmez.
Alıştırma: Uygun ilişkiyi seç
Dog–Animal, Order–Customer, Laptop–Battery, Teacher–Course, AdminUser–User çiftlerinin her biri için is-a, has-a ya da “ikisi de değil” cevaplarından birini seçin. is-a seçtiğiniz her durumda ayrıca “üst tür beklenen yerde alt tür güvenle kullanılabilir mi?” sorusunu yanıtlayın.
Alıştırma: Override tamamla
returnself.width *self.height
Note
Bir alt sınıf area() metodunu yazmazsa, o sınıfın nesnesinde area() çağrıldığında NotImplementedError çıkar. Böylece “bu metodu alt sınıf yazmalı” beklentisi koda açıkça yazılmış olur. Bu, öğretim için seçilmiş bir örnektir ve sınıfı teknik olarak soyut (nesnesi oluşturulamayan) yapmaz: Shape() nesnesi yine oluşturulabilir. NotImplemented ile NotImplementedError arasındaki farkı 12. haftada göreceğiz.
Alıştırma: Normal metotta super() kullan
Bir Logger.log(message) metodu "LOG: ..." döndürsün. TimestampLogger, log() metodunu override etsin; önce super().log(message) çağırıp sonra sonuca " [zaman damgalı]" eklesin. Üst metodu kopyalamadan davranışı genişletin.
Sıra sizde: Ödeme türleri
Bir Payment üst sınıfı ile CardPayment ve CashPayment alt sınıflarını tasarlayın. İkisinde de pay(amount) metodu olsun. En az bir alt sınıfta super() ile normal bir üst metodu genişletin.
Çalışır kod görevi: İki alt sınıftan birer nesneyi aynı listeye koyun ve bir döngüde her birinin pay() metodunu çağırın. isinstance(child, Parent) sonucunu assert ile doğrulayın. Son olarak yanlış kurulmuş bir kalıtım örneği yazın ve yerine geçme ilkesini (LSP) neden bozduğunu iki cümleyle açıklayın.
Sınav provası
Önce kendi cevabınızı seçin; sonra cevap anahtarında her şıkkın neden doğru veya yanlış olduğunu okuyun. Çeldiriciler uydurma değil, bu konuda gerçekten yapılan hatalardır. Sınav maddeleri de bu mantıkla yazılır.
A. Önce Bildirim: Ders 10.00, sonra SMS: Ders 10.00, sonra genel
B. Önce Bildirim: Ders 10.00, sonra genel
C. Önce SMS: Ders 10.00, sonra genel
D. Önce SMS: Ders 10.00, sonra AttributeError
E. Önce SMS: Ders 10.00, sonra None
TipCevaplar ve gerekçeler
Madde 1. Doğru: D.Developer gövdesi boş olsa da __init__ ve describe üst sınıftan devralınır. Bir Developer nesnesi aynı zamanda bir Employee sayılır.
A yanlış:Developer metodu override etmemiştir; üst sınıfın metni kullanılır.
B yanlış: Gövdesi yalnızca pass olan sınıf geçerlidir; nesnesi oluşturulabilir.
C yanlış:describe üst sınıfta tanımlıdır ve bulunur.
E yanlış:isinstance() üst sınıfları da dikkate alır.
Madde 2. Doğru: E. Yerine geçme sorusu (Liskov ilkesi), üst sınıfın beklendiği yerde alt sınıf kullanıldığında sözleşmenin bozulup bozulmadığını sınar.
A yanlış:super().__init__() kalıtımla birlikte öğrenildiği için ölçüt gibi görünebilir. Ama kurucuyu kullanabilmek yerine geçme sorusunu cevaplamaz.
B yanlış: Yan yana yazılan sınıflar arasında bir bağ varmış gibi görünür. Ama sınıfların hangi dosyada durduğu tür ilişkisini değiştirmez.
C yanlış: Kalıtım “genişletme” diye anlatıldığı için yeni metot eklemek ölçüt gibi görünür. Ama metot eklemek, alt sınıfın üst türün yerine geçip geçemeyeceğini söylemez.
D yanlış: Ortak nitelikler iki sınıfın akraba olduğunu düşündürür. Ama iki sınıfta aynı nitelik bulunması, birini ötekinden türetmek için gerekçe olmaz.
Madde 3. Doğru: C.Rectangle bekleyen kod, genişliği ve yüksekliği bağımsız ayarlayabileceğini varsayar. Kareyle çalışırken bir kenarı değiştirmek ötekini de değiştirir ve kod şaşırtıcı sonuç alır. is-a cümlesi kurulabilse de yerine geçme bozulur.
A yanlış: Kare matematikte bir dikdörtgendir, ama programdaki Rectangle genişliğin ve yüksekliğin ayrı ayrı değişebileceğine söz verir. Sorun bu sözdedir.
B yanlış: Kare tek kenar uzunluğuyla tanımlandığı için bazı niteliklerin eksik kalacağı sanılabilir. Ama devralınan nitelikler kaybolmaz; sorun hata değil, beklenmedik davranıştır.
D yanlış: Sorun bir dil kısıtı gibi görünebilir. Böyle bir kısıt yoktur; sorun tasarımdadır.
E yanlış: Kare tek, dikdörtgen iki ölçüyle kurulduğu için kurucular uyuşmuyor gibi görünür. Ama alt sınıfın kurucusu üst sınıfınkinden farklı sayıda parametre alabilir.
Madde 4. Doğru: B. Alt sınıftaki send üst sınıftaki aynı adlı metodu ezer; Python metodu önce nesnenin kendi sınıfında arar.
A yanlış: Üst sınıf metodu ancak alt sınıfta aynı ad tanımlı değilse çalışır.
C yanlış: Farklı sınıflarda aynı ad kullanılabilir; override budur.
D yanlış: Override ile üst metot kendiliğinden çağrılmaz.
E yanlış: Metot bir metin döndürür.
Madde 5. Doğru: E.super().send(message) üst sınıfın send metodunu çalıştırır; alt sınıf dönen metne kendi ekini ekler.
A yanlış: Alt sınıf sonuca kendi ekini de katar.
B yanlış:super() bir üstteki sınıfın metodunu çağırır; özyineleme oluşmaz.
C yanlış:super() her metotta kullanılabilir; bu kod da bunun örneğidir.
D yanlış: Üst sınıfın metni de sonuca girer.
Madde 6. Doğru: A. Üst sınıfın __init__ içindeki doğrulama ve atamalar kopyalanmadan kullanılır; kural değişince tek yer güncellenir.
B yanlış:super() bir üst sınıf nesnesi veriyormuş gibi okunabilir. Ama kalıtımda ikinci bir nesne oluşmaz; aynı örnek başlatılır.
C yanlış: Çoğu örnekte yazıldığı için Python’un bunu zorunlu tuttuğu sanılabilir. Zorunlu değildir; yazılmazsa üst kurulum hiç çalışmaz.
D yanlış:super() üst sınıfla bağ kurduğu için override’ı da o sağlıyormuş gibi görünür. Override için aynı adla metot yazmak yeter; super() gerekmez.
E yanlış:super() çağrısından sonra nesne hazır göründüğü için alt sınıfın nitelikleri de kurulmuş sanılabilir. Ama alt sınıfın kendi nitelikleri ayrıca yazılır.
Madde 7. Doğru: B. Ortak bir alan bulunması is-a ilişkisi kurmaz. Driver is a Car cümlesi anlamsızdır; ortak nitelik yalnız bir rastlantıdır.
A yanlış: Aynı alanı iki kez yazmamak için tek sınıf kısa yol gibi görünür. Ama sürücü ile arabanın sorumlulukları ayrıdır.
C yanlış: Ortak nitelik, bir sınıfın ötekinden türeyebileceğini düşündürür. Ama sürücü bir araba değildir.
D yanlış: Ortak alanı tek yerde toplamak tekrarı azaltıyor gibi görünür. Ama yalnızca aynı adlı bir nitelik taşımaları, ortak bir üst tür gerektirmez.
E yanlış: Arabanın bir sürücüsü olabileceği için bu seçenek doğru bir ilişkiyi anlatıyor gibi görünür. Gerekebilir ama bunu name alanının ortaklığı söylemez.
Madde 8. Doğru: D. Bağ sıkıdır; değişikliğin nereleri etkilediği izlenemez hâle gelir. Bu yüzden bu derste iki-üç seviyeden derin hiyerarşi tasarlanmaz ve kompozisyon tercih edilir.
A yanlış: Her seviye ayrı bir nesne kuruyormuş gibi düşünülürse bellek sorunu akla gelir. Ama kalıtımda tek bir nesne vardır; sorun bellekte değil bakımdadır: bir değişikliğin nereleri etkilediğini izlemek zorlaşır.
B yanlış: Zincir uzadıkça super() çağrılarının kopacağı sanılabilir. Ama her sınıf super() çağırdıkça çağrı zincir boyunca ilerler.
C yanlış: Uzun zincirde en üstteki sınıf uzakta kalmış gibi görünür. Ama alt sınıflar üst sınıf metotlarını çağırabilir; sorun, alt sınıfların üst sınıfa bu kadar sıkı bağlı olmasıdır.
E yanlış: Derste iki-üç seviye sınırı konduğu için bunun bir dil kuralı olduğu sanılabilir. Teknik bir sınır yoktur; sınır tasarım kararıdır.
Madde 9. Doğru: A. İyi tasarımda amaç çok sayıda üst ve alt sınıf kurmak değil, parçaları kolayca değiştirilebilir tutmaktır.
B yanlış: Gerekçe başarım değildir.
C yanlış: Test edilebilir; sorun bağın sıkılığıdır.
D yanlış: Python çoklu kalıtımı destekler; yalnızca bu dersin kapsamı dışında bırakıldı.
E yanlış: Kendi sınıflarımızda da yerinde kullanılabilir.
Madde 10. Doğru: C.send alt sınıfta override edildiği için alt sınıftaki çalışır. channel alt sınıfta yok, üst sınıftan devralınır.
A yanlış: Override üst sınıftaki metodu kendiliğinden çağırmaz. Bunun için super().send(...) yazmak gerekir.
B yanlış: Metot önce nesnenin kendi sınıfında aranır. SmsNotification kendi send metoduna sahip.
D yanlış: Alt sınıfta tanımlanmayan metot hata vermez, üst sınıftan gelir. Kalıtımın amacı bu.
E yanlış: Devralınan metot üst sınıftaki gövdesiyle çalışır ve "genel" döndürür.
Tek sayfa özet
Kalıtım bir is-a ilişkisidir; alt tür üst türün beklenen sözleşmesini korumalıdır.
Bu beklentinin adı Liskov yerine geçme ilkesidir: üst türün beklendiği yerde alt tür de sorunsuz çalışmalıdır.
Alt sınıf üst sınıfın metodunu olduğu gibi devralabilir, yeni metot ekleyebilir ya da metodu override edebilir.
super() yalnızca kurucuda değil, normal bir metodun sonucunu genişletirken de kullanılır.
isinstance, alt sınıfın örneğini üst sınıfın örneği olarak da tanır.
Bir metot önce nesnenin kendi sınıfında aranır, orada yoksa üst sınıftan gelir.
Kalıtıma karar vermeden önce kompozisyonu da düşünün: “sahiptir” cümlesi daha doğru duruyorsa kompozisyon seçin.
Bu bölümün kazanımları
Bu bölümü bitiren öğrenci:
Alt sınıfın üst sınıftan davranış devraldığını ve üst sınıfın bir alt türü sayıldığını gösterir.
Kalıtım kararını is-a sezgisiyle değil, yerine geçme sorusuyla sınar.
Bir metodu override ederek davranışı özelleştirir.
super() çağrısını yalnız kurucuda değil normal metotta da davranışı genişletmek için kullanır.
is-a ile has-a ilişkisini ayırır ve ortak bir niteliği kalıtım gerekçesi saymaz.
Derin kalıtım zincirinin kırılganlığını açıklar.
Override edilen ve devralınan metotların çağrıda hangi sınıftan geldiğini kod izleyerek belirler.