Liskov Yerine Geçme Prensibi (Liskov Substitution Principle — LSP)
Nesne Yönelimli Programlamanın (OOP) temelini oluşturan SOLID prensipleri arasında, belki de anlaşılması en kritik ve kalıtım mekanizmasının doğru kullanımını en çok etkileyen ilke, Liskov Yerine Geçme Prensibi (Liskov Substitution Principle — LSP)’dir. Barbara Liskov tarafından 1987'deki bir konferansta sunulan ve daha sonra Jeannette Wing ile birlikte makale haline getirilen bu prensip, SOLID kısaltmasının ‘L’ harfini temsil eder. LSP, temel olarak alt sınıfların (subclasses), üst sınıflarının (superclasses) yerine davranışsal olarak tutarlı bir şekilde geçebilmesi gerektiğini belirtir. Yani, bir programın herhangi bir yerinde üst sınıf türünden bir nesne bekleniyorsa, o programın doğruluğunu veya beklentilerini bozmadan, oraya o üst sınıftan türetilmiş herhangi bir alt sınıf nesnesini koyabilmeliyiz. Bu, kalıtımın sadece kod paylaşımı için değil, aynı zamanda tür uyumluluğu (type compatibility) ve davranışsal alt tipleme (behavioral subtyping) için de doğru bir şekilde kullanılmasını sağlamayı hedefler. Bu rehberde, Liskov Yerine Geçme Prensibi’nin ne anlama geldiğini, neden bu kadar önemli olduğunu, ihlal edildiğinde ortaya çıkan sorunları ve Python’da bu prensibe nasıl uyulabileceğini detaylı bir şekilde inceleyeceğiz. Özellikle meşhur “Kare-Dikdörtgen Problemi” üzerinden LSP ihlalini somutlaştıracak ve prensibe uygun tasarım stratejilerini tartışacağız. Bölüm 1: Liskov Yerine Geçme Prensibi (LSP) Nedir? Tanım (Barbara Liskov ve Jeannette Wing) Let Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S where S is a subtype of T. (Türkçesi: T türündeki nesneler x hakkında ispatlanabilir bir özellik Φ(x) olsun. Eğer S, T’nin bir alt tipi ise, S türündeki nesneler y için de Φ(y) doğru olmalıdır.) Bu formal tanım, biraz akademik görünebilir. Daha basit bir ifadeyle LSP şunu söyler: Alt sınıflar, türetildikleri üst sınıfların yerine, programın beklenen davranışını değiştirmeden veya bozmadan kullanılabilmelidir. Anahtar Fikirler: Yerine Geçebilirlik (Substitutability): Alt sınıf nesneleri, üst sınıf nesnelerinin kullanıldığı her yerde güvenle kullanılabilmelidir. Davranışsal Uyumluluk (Behavioral Compatibility): Bu sadece metot imzalarının (isim, parametreler) aynı olması demek değildir. Alt sınıf, üst sınıfın tanımladığı kontratı (beklenen davranışı, önkoşulları, sonkoşulları, değişmezleri) da yerine getirmelidir. Alt sınıf, üst sınıfın davranışını beklenmedik veya uyumsuz bir şekilde değiştirmemelidir. Beklentileri Bozmamak: Üst sınıfı kullanan kod (istemci kod), bir alt sınıf nesnesiyle çalıştığında “şaşırmamalı” veya hata vermemelidir. Alt sınıf, üst sınıfın vaat ettiği her şeyi yerine getirmelidir. Analoji: Uçak ve Oyuncak Uçak Bir Uçak sınıfımız ve onun bir uc() metodu olduğunu düşünelim. Bir de OyuncakUcak sınıfı tanımlayıp onu Uçak sınıfından miras aldırırsak, "Oyuncak Uçak bir Uçak'tır" ilişkisi mantıksal olarak yanlıştır. Bir uçuş kontrol sistemi Uçak nesneleriyle çalışmak üzere tasarlanmışsa, ona bir OyuncakUcak nesnesi verdiğimizde sistemin beklentileri (gerçekten uçması, belirli bir hızla gitmesi vb.) karşılanmayacaktır. Oyuncak uçak, gerçek uçağın yerine geçemez çünkü davranışsal olarak uyumlu değildir. Bu, LSP'nin ihlal edildiği bir durumdur. Kalıtım yerine başka bir ilişki (belki kompozisyon) daha uygun olurdu. Bölüm 2: LSP Neden Önemli? Faydaları Neler? Liskov Yerine Geçme Prensibi’ne uymak, OOP tasarımlarına önemli katkılar sağlar: Güvenilir Kalıtım Hiyerarşileri: LSP, kalıtımın sadece kod paylaşımı için değil, aynı zamanda mantıksal ve davranışsal bir “Is-A” ilişkisini doğru bir şekilde modellemek için kullanılmasını sağlar. Bu, oluşturulan sınıf hiyerarşilerinin daha sağlam ve güvenilir olmasını garanti eder. Öngörülebilir Polimorfizm: Polimorfizmin (farklı nesnelerin aynı arayüze farklı yanıt vermesi) düzgün çalışabilmesi için LSP kritiktir. Eğer alt sınıflar üst sınıfların yerine güvenle geçemiyorsa, üst sınıf referansı üzerinden yapılan polimorfik çağrılar beklenmedik hatalara veya yanlış davranışlara yol açabilir. LSP, polimorfik kodun öngörülebilir ve doğru çalışmasını sağlar. Kodun Doğruluğu ve Kararlılığı: Bir fonksiyon veya modül, belirli bir üst sınıf türüyle çalışmak üzere tasarlandığında, LSP sayesinde o sınıfın herhangi bir alt sınıfıyla da güvenle çalışabileceğinden emin olabilir. Bu, kodun genel doğruluğunu ve kararlılığını artırır. Daha Kolay Bakım ve Değişim: LSP’ye uygun sistemlerde, bir alt sınıfı değiştirmek veya yeni bir alt sınıf eklemek, üst sınıfı kullanan mevcut kodları bozma riski taşımaz (eğer alt sınıf kontrata uyuyorsa). Bu, bakımı ve sistemi geliştirmeyi kolaylaştırır. Açık/Kapalı Prensibi (OCP) Desteği: LSP, OCP’nin (Genişletmeye Açık, Değiştirmeye Kapalı) doğru bir şekilde uygulanabilmesi için temel bir gerekliliktir. Eğer alt sınıflar üst sınıfların yerine geçemiyorsa, sisteme yeni alt sınıflar ekleyerek genişletme yapmak (OCP’nin “açık” kısmı) mevcut kodu bozabilir (OCP’nin “kapalı” kısmını ihlal

Nesne Yönelimli Programlamanın (OOP) temelini oluşturan SOLID prensipleri arasında, belki de anlaşılması en kritik ve kalıtım mekanizmasının doğru kullanımını en çok etkileyen ilke, Liskov Yerine Geçme Prensibi (Liskov Substitution Principle — LSP)’dir. Barbara Liskov tarafından 1987'deki bir konferansta sunulan ve daha sonra Jeannette Wing ile birlikte makale haline getirilen bu prensip, SOLID kısaltmasının ‘L’ harfini temsil eder.
LSP, temel olarak alt sınıfların (subclasses), üst sınıflarının (superclasses) yerine davranışsal olarak tutarlı bir şekilde geçebilmesi gerektiğini belirtir. Yani, bir programın herhangi bir yerinde üst sınıf türünden bir nesne bekleniyorsa, o programın doğruluğunu veya beklentilerini bozmadan, oraya o üst sınıftan türetilmiş herhangi bir alt sınıf nesnesini koyabilmeliyiz. Bu, kalıtımın sadece kod paylaşımı için değil, aynı zamanda tür uyumluluğu (type compatibility) ve davranışsal alt tipleme (behavioral subtyping) için de doğru bir şekilde kullanılmasını sağlamayı hedefler.
Bu rehberde, Liskov Yerine Geçme Prensibi’nin ne anlama geldiğini, neden bu kadar önemli olduğunu, ihlal edildiğinde ortaya çıkan sorunları ve Python’da bu prensibe nasıl uyulabileceğini detaylı bir şekilde inceleyeceğiz. Özellikle meşhur “Kare-Dikdörtgen Problemi” üzerinden LSP ihlalini somutlaştıracak ve prensibe uygun tasarım stratejilerini tartışacağız.
Bölüm 1: Liskov Yerine Geçme Prensibi (LSP) Nedir?
Tanım (Barbara Liskov ve Jeannette Wing)
Let Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S where S is a subtype of T.
(Türkçesi: T türündeki nesneler x hakkında ispatlanabilir bir özellik Φ(x) olsun. Eğer S, T’nin bir alt tipi ise, S türündeki nesneler y için de Φ(y) doğru olmalıdır.)
Bu formal tanım, biraz akademik görünebilir. Daha basit bir ifadeyle LSP şunu söyler:
Alt sınıflar, türetildikleri üst sınıfların yerine, programın beklenen davranışını değiştirmeden veya bozmadan kullanılabilmelidir.
Anahtar Fikirler:
Yerine Geçebilirlik (Substitutability): Alt sınıf nesneleri, üst sınıf nesnelerinin kullanıldığı her yerde güvenle kullanılabilmelidir.
Davranışsal Uyumluluk (Behavioral Compatibility): Bu sadece metot imzalarının (isim, parametreler) aynı olması demek değildir. Alt sınıf, üst sınıfın tanımladığı kontratı (beklenen davranışı, önkoşulları, sonkoşulları, değişmezleri) da yerine getirmelidir. Alt sınıf, üst sınıfın davranışını beklenmedik veya uyumsuz bir şekilde değiştirmemelidir.
Beklentileri Bozmamak: Üst sınıfı kullanan kod (istemci kod), bir alt sınıf nesnesiyle çalıştığında “şaşırmamalı” veya hata vermemelidir. Alt sınıf, üst sınıfın vaat ettiği her şeyi yerine getirmelidir.
Analoji: Uçak ve Oyuncak Uçak
Bir Uçak sınıfımız ve onun bir uc() metodu olduğunu düşünelim. Bir de OyuncakUcak sınıfı tanımlayıp onu Uçak sınıfından miras aldırırsak, "Oyuncak Uçak bir Uçak'tır" ilişkisi mantıksal olarak yanlıştır. Bir uçuş kontrol sistemi Uçak nesneleriyle çalışmak üzere tasarlanmışsa, ona bir OyuncakUcak nesnesi verdiğimizde sistemin beklentileri (gerçekten uçması, belirli bir hızla gitmesi vb.) karşılanmayacaktır. Oyuncak uçak, gerçek uçağın yerine geçemez çünkü davranışsal olarak uyumlu değildir. Bu, LSP'nin ihlal edildiği bir durumdur. Kalıtım yerine başka bir ilişki (belki kompozisyon) daha uygun olurdu.
Bölüm 2: LSP Neden Önemli? Faydaları Neler?
Liskov Yerine Geçme Prensibi’ne uymak, OOP tasarımlarına önemli katkılar sağlar:
Güvenilir Kalıtım Hiyerarşileri: LSP, kalıtımın sadece kod paylaşımı için değil, aynı zamanda mantıksal ve davranışsal bir “Is-A” ilişkisini doğru bir şekilde modellemek için kullanılmasını sağlar. Bu, oluşturulan sınıf hiyerarşilerinin daha sağlam ve güvenilir olmasını garanti eder.
Öngörülebilir Polimorfizm: Polimorfizmin (farklı nesnelerin aynı arayüze farklı yanıt vermesi) düzgün çalışabilmesi için LSP kritiktir. Eğer alt sınıflar üst sınıfların yerine güvenle geçemiyorsa, üst sınıf referansı üzerinden yapılan polimorfik çağrılar beklenmedik hatalara veya yanlış davranışlara yol açabilir. LSP, polimorfik kodun öngörülebilir ve doğru çalışmasını sağlar.
Kodun Doğruluğu ve Kararlılığı: Bir fonksiyon veya modül, belirli bir üst sınıf türüyle çalışmak üzere tasarlandığında, LSP sayesinde o sınıfın herhangi bir alt sınıfıyla da güvenle çalışabileceğinden emin olabilir. Bu, kodun genel doğruluğunu ve kararlılığını artırır.
Daha Kolay Bakım ve Değişim: LSP’ye uygun sistemlerde, bir alt sınıfı değiştirmek veya yeni bir alt sınıf eklemek, üst sınıfı kullanan mevcut kodları bozma riski taşımaz (eğer alt sınıf kontrata uyuyorsa). Bu, bakımı ve sistemi geliştirmeyi kolaylaştırır.
Açık/Kapalı Prensibi (OCP) Desteği: LSP, OCP’nin (Genişletmeye Açık, Değiştirmeye Kapalı) doğru bir şekilde uygulanabilmesi için temel bir gerekliliktir. Eğer alt sınıflar üst sınıfların yerine geçemiyorsa, sisteme yeni alt sınıflar ekleyerek genişletme yapmak (OCP’nin “açık” kısmı) mevcut kodu bozabilir (OCP’nin “kapalı” kısmını ihlal eder).
Daha İyi Soyutlamalar: LSP, üst sınıfların ve arayüzlerin davranışsal kontratlarını dikkatlice düşünmeye teşvik eder. Bu da daha iyi tanımlanmış ve daha kullanışlı soyutlamalarla sonuçlanır.
Azaltılmış Tip Kontrolü İhtiyacı: Eğer LSP’ye uyuluyorsa, kod içinde sürekli olarak if isinstance(nesne, AltSinif1): ... elif isinstance(nesne, AltSinif2): ... gibi tip kontrolleri yapma ihtiyacı azalır. Çünkü üst sınıf referansı üzerinden yapılan işlemlerin tüm alt sınıflar için tutarlı çalışacağı varsayılabilir.
Bölüm 3: LSP İhlalleri: Ne Zaman Yanlış Gider?
Bir alt sınıfın üst sınıfın yerine geçemediği durumlar, LSP ihlalleridir. Bu ihlaller genellikle alt sınıfın, üst sınıfın kontratını (beklenen davranışını) bozmasıyla ortaya çıkar. İşte yaygın ihlal senaryoları ve belirtileri:
Override Edilen Metotta Daha Güçlü Önkoşullar (Preconditions):
İhlal: Alt sınıf metodu, üst sınıf metodunun kabul ettiğinden daha kısıtlı veya daha fazla sayıda/türde argüman gerektiriyorsa.
Sonuç: Üst sınıf türüyle çalışan kod, alt sınıf nesnesine geçerli olması gereken bir argüman verdiğinde alt sınıf metodu hata verebilir.
Kural: Alt sınıf metodunun önkoşulları, üst sınıf metodunun önkoşullarından daha güçlü olamaz (en fazla aynı veya daha zayıf olabilir).
Override Edilen Metotta Daha Zayıf Sonkoşullar (Postconditions):
İhlal: Alt sınıf metodu, üst sınıf metodunun garanti ettiği bir sonucu (örneğin, belirli bir aralıkta değer döndürme, belirli bir durumu sağlama) sağlamıyorsa veya daha azını sağlıyorsa.
Sonuç: Üst sınıf metodunun sonucuna güvenen kod, alt sınıf nesnesi kullanıldığında yanlış çalışabilir.
Kural: Alt sınıf metodunun sonkoşulları, üst sınıf metodunun sonkoşullarından daha zayıf olamaz (en fazla aynı veya daha güçlü olabilir).
Override Edilen Metotta Yeni İstisnalar Fırlatma:
İhlal: Alt sınıf metodu, üst sınıf metodunun fırlatmadığı (ve üst sınıfın beklenen istisna türlerinden olmayan) yeni türde istisnalar fırlatıyorsa.
Sonuç: Üst sınıf için yazılmış hata yönetimi (try...except) blokları, alt sınıftan gelen bu beklenmedik istisnaları yakalayamayabilir ve program çökebilir.
Kural: Alt sınıf metodu, üst sınıf metodunun fırlatabileceği istisna türlerinin alt türleri olan istisnaları fırlatabilir, ancak tamamen yeni ve beklenmedik türler fırlatmamalıdır.
Üst Sınıf Değişmezlerini (Invariants) Bozma:
İhlal: Üst sınıfın her zaman koruduğu varsayılan bir durumu veya kuralı (invariant), alt sınıfın bir metodu bozuyorsa.
Sonuç: Üst sınıfın veya onu kullanan diğer kodların bu değişmeze güvendiği durumlarda hatalar oluşabilir.
Metotları Boş Bırakma veya NotImplementedError
Fırlatma:
İhlal: Alt sınıf, miras aldığı ve üst sınıf kontratının parçası olan bir metodu implemente etmek yerine boş bırakıyor (pass) veya her zaman NotImplementedError fırlatıyorsa. Bu, alt sınıfın aslında üst sınıfın tüm davranışlarını desteklemediğini gösterir.
Sonuç: Üst sınıf referansı üzerinden bu metot çağrıldığında program ya hiçbir şey yapmaz ya da hata verir.
Kural: Eğer bir alt sınıf, üst sınıfın bazı metotlarını mantıksal olarak destekleyemiyorsa, bu genellikle kalıtımın yanlış kurulduğunun (“Is-A” ilişkisinin zayıf olduğunun) bir işaretidir.
İstemci Kodda Tip Kontrolü (isinstance
) Zorunluluğu:
İhlal Belirtisi: Üst sınıf türünden bir nesneyle çalışması gereken kodun içinde, nesnenin spesifik alt tipini kontrol edip (if isinstance(obj, AltTip1): ...) ona göre farklı davranma ihtiyacı duyuluyorsa, bu genellikle alt sınıfların üst sınıfın yerine tam olarak geçemediğinin bir göstergesidir.
Bölüm 4: Klasik Örnek: Kare-Dikdörtgen Problemi
LSP ihlalini anlamak için en sık kullanılan örnek, Kare’nin Dikdörtgen’den miras alması durumudur.
Matematiksel olarak bir kare, kenarları eşit uzunlukta olan özel bir dikdörtgendir. Bu nedenle, OOP’de Kare sınıfını Dikdortgen sınıfından türetmek ilk başta mantıklı görünebilir ("Kare bir Dikdörtgen'dir" gibi).
LSP İhlali — Kare ve Dikdörtgen
class Dikdortgen:
def init(self, genislik: float, yukseklik: float):
# Nitelikleri korumalı yapalım (konvansiyon)
self.genislik = genislik
self._yukseklik = yukseklik
@property
def genislik(self) -> float:
return self._genislik
@genislik.setter
def genislik(self, value: float):
print(f"Dikdörtgen genişlik {value} olarak ayarlandı.")
self._genislik = value
@property
def yukseklik(self) -> float:
return self._yukseklik
@yukseklik.setter
def yukseklik(self, value: float):
print(f"Dikdörtgen yükseklik {value} olarak ayarlandı.")
self._yukseklik = value
def alan(self) -> float:
return self.genislik * self.yukseklik
class Kare(Dikdortgen):
def __init(self, kenar: float):
# Kare'nin genişliği ve yüksekliği aynıdır
print(f"Kare (kenar={kenar}) oluşturuluyor...")
super().init_(kenar, kenar)
# !!! LSP İHLALİ BURADA BAŞLIYOR !!!
# Kare özelliğini korumak için setter'ları override ediyoruz
@Dikdortgen.genislik.setter
def genislik(self, value: float):
print(f"Kare kenarı (genişlik üzerinden) {value} olarak ayarlandı.")
self._genislik = value
self._yukseklik = value # Yan etki: Yüksekliği de değiştir!
@Dikdortgen.yukseklik.setter
def yukseklik(self, value: float):
print(f"Kare kenarı (yükseklik üzerinden) {value} olarak ayarlandı.")
self._genislik = value # Yan etki: Genişliği de değiştir!
self._yukseklik = value
Dikdörtgen bekleyen bir istemci kod
def test_dikdortgen(dikdortgen: Dikdortgen):
print(f"\n--- {type(dikdortgen).name} Test Ediliyor ---")
ilk_genislik = 10
ilk_yukseklik = 20
dikdortgen.genislik = ilk_genislik # Genişliği ayarla
dikdortgen.yukseklik = ilk_yukseklik # Yüksekliği ayarla
# İstemcinin Beklentisi (Dikdörtgen Kontratı):
# Genişliği ayarlamak yüksekliği etkilememeli,
# Yüksekliği ayarlamak genişliği etkilememeli.
# Alan = ayarlanan genişlik * ayarlanan yükseklik olmalı.
beklenen_alan = ilk_genislik * ilk_yukseklik
hesaplanan_alan = dikdortgen.alan()
print(f"Ayarlanan G: {ilk_genislik}, Ayarlanan Y: {ilk_yukseklik}")
print(f"Son Durum: G={dikdortgen.genislik}, Y={dikdortgen.yukseklik}")
print(f"Beklenen Alan: {beklenen_alan}")
print(f"Hesaplanan Alan: {hesaplanan_alan}")
if hesaplanan_alan == beklenen_alan:
print(">>> Test Başarılı!")
else:
print(">>> Test BAŞARISIZ! (LSP İhlali)")
Test
d = Dikdortgen(2, 3)
k = Kare(5)
test_dikdortgen(d) # Başarılı
test_dikdortgen(k) # BAŞARISIZ!
Neden Başarısız Oldu? (LSP İhlali Açıklaması)
test_dikdortgen fonksiyonu, bir Dikdortgen nesnesiyle çalışmak üzere tasarlanmıştır. Bu tasarımın altında yatan örtük bir varsayım (kontrat) vardır: Genişliği değiştirmenin yüksekliği etkilememesi ve yüksekliği değiştirmenin genişliği etkilememesi.
Kare sınıfı, bu kontratı bozar. Kare özelliğini korumak için, genişlik ayarlandığında yüksekliği de değiştirir (ve tersi).
test_dikdortgen fonksiyonuna Kare nesnesi verildiğinde, dikdortgen.yukseklik = ilk_yukseklik (yani kare.yukseklik = 20) ataması yapılır. Ancak bu atama, Kare'nin override edilmiş setter'ı nedeniyle hem yüksekliği hem de genişliği 20 yapar.
Sonuç olarak, fonksiyon alan()'ı çağırdığında, beklediği 10 * 20 = 200 alanı yerine 20 * 20 = 400 alanını bulur.
Kare nesnesi, Dikdortgen nesnesinin yerine güvenle geçememiştir çünkü üst sınıfın davranışsal beklentilerini (kontratını) ihlal etmiştir. Bu klasik bir LSP ihlalidir.
Bölüm 5: Python’da LSP’ye Uyum Sağlama Stratejileri
LSP’ye uygun kod yazmak, dikkatli tasarım ve kalıtım ilişkilerini doğru kurmayı gerektirir.
5.1. “Is-A” İlişkisini Dikkatlice Değerlendirme
Kalıtım kullanmadan önce kendinize şu soruyu sorun: “Alt sınıf gerçekten üst sınıfın bir türü mü ve onun tüm davranışsal kontratını yerine getirebilir mi?” Eğer cevap “hayır” veya “bazı durumlarda evet” ise, kalıtım muhtemelen yanlış modeldir.
Kare-Dikdörtgen örneğinde, kare matematiksel olarak özel bir dikdörtgen olsa da, davranışsal olarak (genişlik ve yüksekliğin bağımsız değiştirilebilirliği açısından) bir dikdörtgenin yerine tam olarak geçemez. Bu nedenle, kalıtım burada sorunludur.
5.2. Üst Sınıf Kontratlarına Saygı Gösterme
Bir metodu override ederken, üst sınıf metodunun amacını, önkoşullarını, sonkoşullarını ve fırlatabileceği istisnaları anlayın ve bunlara uyun:
Önkoşulları Güçlendirmeyin: Alt sınıf metodu, üst sınıf metodundan daha kısıtlayıcı olmamalıdır. Örneğin, üst sınıf her türlü sayıyı kabul ediyorsa, alt sınıf sadece pozitif sayıları kabul etmemelidir.
Sonkoşulları Zayıflatmayın: Alt sınıf metodu, üst sınıf metodunun sağladığı garantilerden daha azını sağlamamalıdır. Örneğin, üst sınıf her zaman sıralı bir liste döndürmeyi garanti ediyorsa, alt sınıf sırasız bir liste döndürmemelidir.
Yeni Beklenmedik İstisnalar Fırlatmayın: Alt sınıf, istemci kodun beklemediği ve handle edemeyeceği yeni istisna türleri fırlatmamalıdır.
Değişmezleri (Invariants) Koruyun: Alt sınıf metotları, üst sınıfın durumunu tutarlı tutan temel kuralları bozmamalıdır.
5.3. Kısıtlayıcı Metotlardan Kaçınma
Alt sınıf, miras aldığı bir metodu anlamsız kılacak şekilde (örneğin, her zaman NotImplementedError fırlatarak veya hiçbir şey yapmayarak) override etmemelidir. Bu genellikle "Is-A" ilişkisinin zayıf olduğunun bir işaretidir.
Kötü Örnek
class Kus:
def uc(self): print("Kuş uçuyor")
class Penguen(Kus):
def uc(self): # LSP İhlali Potansiyeli
# Penguenler uçamaz!
raise NotImplementedError("Penguenler uçamaz!")
# veya pass
Penguen beklemeyen bir kod uc() çağırırsa hata alır veya hiçbir şey olmaz.
Bu, Penguen'in Kus kontratına tam uymadığını gösterir.
Belki de 'UcamayanKus' gibi bir ara sınıf veya farklı bir hiyerarşi gerekir.
5.4. Kompozisyonu Kalıtıma Tercih Etme (Gerektiğinde)
Eğer “Is-A” ilişkisi zayıfsa veya LSP sorunları yaratıyorsa, kalıtım yerine kompozisyon kullanmayı düşünün. Kare-Dikdörtgen problemi için bir çözüm, her ikisini de ayrı tutmak veya belki ortak bir Sekil soyut sınıfından türetmektir.
Alternatif Tasarım (LSP Sorunu Yok)
import abc
class Sekil(abc.ABC):
@abc.abstractmethod
def alan(self): pass
class Dikdortgen(Sekil):
def init(self, genislik, yukseklik):
self.genislik = genislik
self.yukseklik = yukseklik
def alan(self):
return self.genislik * self.yukseklik
# ... genişlik/yükseklik için getter/setter (bağımsız) ...
class Kare(Sekil):
def init(self, kenar):
self.kenar = kenar
def alan(self):
return self.kenar ** 2
# ... kenar için getter/setter ...
Şimdi Kare, Dikdörtgen'in kontratını bozmaz çünkü ondan miras almıyor.
Her ikisi de Sekil kontratına uyuyor.
d = Dikdortgen(10, 20)
k = Kare(10)
Şekil bekleyen kod her ikisiyle de çalışabilir (alan metodu üzerinden)
sekiller = [d, k]
for s in sekiller:
print(f"{type(s).name} Alanı: {s.alan()}")
5.5. Arayüzleri (ABCs / Protocols) Kullanma
Davranışsal kontratları açıkça tanımlamak için Soyut Temel Sınıfları (ABCs) veya Protokolleri kullanmak, LSP’ye uygunluğu teşvik eder. ABC’ler, alt sınıfların belirli metotları implemente etmesini zorunlu kılarak kontratın yerine getirilmesine yardımcı olur.
Bölüm 6: Sonuç: Davranışsal Tutarlılığın Önemi
Liskov Yerine Geçme Prensibi (LSP), Nesne Yönelimli Tasarımın temel bir ilkesidir ve kalıtım mekanizmasının doğru ve güvenilir bir şekilde kullanılmasını hedefler. Temel mesajı basittir: Bir alt sınıf, türediği üst sınıfın yerine, programın beklentilerini bozmadan geçebilmelidir.
Bu sadece metot imzalarının eşleşmesi değil, aynı zamanda davranışsal tutarlılığın korunması anlamına gelir. Alt sınıflar, üst sınıfların kontratlarını (önkoşullar, sonkoşullar, değişmezler, fırlatılan istisnalar) yerine getirmelidir.
LSP’ye uymak:
Daha güvenilir ve öngörülebilir kod tabanları oluşturur.
Polimorfizmin gücünden tam olarak yararlanmamızı sağlar.
Açık/Kapalı Prensibi’nin uygulanmasını destekler.
Bakımı ve genişletmeyi kolaylaştırır.
Gereksiz tip kontrollerini azaltır.
Python gibi dinamik dillerde LSP’nin derleme zamanında zorunlu kılınması mümkün olmasa da, prensibin ruhuna uygun tasarım yapmak geliştiricinin sorumluluğundadır. “Is-A” ilişkisini dikkatlice değerlendirmek, üst sınıf kontratlarına saygı göstermek ve gerektiğinde kalıtım yerine kompozisyon gibi alternatifleri kullanmak, LSP’ye uygun, sağlam ve esnek OOP sistemleri oluşturmanın anahtarlarıdır.
Abdulkadir Güngör - Kişisel WebSite
Abdulkadir Güngör - Kişisel WebSite
Abdulkadir Güngör - Özgeçmiş
Github
Github
Linkedin