green red and yellow wall

Liskov Substitution Principle (Liskov’un Yerine Geçme Prensibi)

Bu prensibin odak noktası; alt seviyedeki nesnelerin, üst seviyedeki nesneler ile yer değiştirebilir olmasını garanti etmek ve mümkün kılmaktır. Alt seviye bir nesne, üst seviyesi nesne ile aynı şekilde davranmalıdır ki bu sayede yer değişmeleri halinde her şeyin aynı şekilde çalışabilmesi sağlanabilsin.

Bu prensibi Open/Closed Principle (Açık Kapalı Prensibi) aklımızın bir köşesinde tutarak anlamaya çalışırsak kavramamız çok daha kolay olacaktır.

Alt seviye nesnelerin bağlı oldukları üst seviye nesneler ile aynı şekilde davranabilmesi ne demek?

Bu prensipe uygun kod geliştirebilmek için; üst seviye nesnelerin aldığı girdiler ile, aynı formatta çıktı verebilen ve aynı davranışlara sahip alt nesnelerimizin olması gerekir. Böyle diyince de hemen anlaşılabilecek bir konu olmadığından hızlıca örnekler üzerinden ilerleyelim; [durun korkmayın her yerde gördüğünüz dikdörtgen, kare örneği değil :)]

Ayrıca örnek sonrasında nesnelerimizin bu prensibe uyum sağlayıp sağlamadığını kontrol etmemiz için baz alacağımız maddeleri iletiyor olacağım.

<?php

interface ICalc {
    public function sum(float $num1, float $num2): float;
}
 
class Calc1 implements ICalc {
    public function sum(float $num1, float $num2): float { 
        return $num1 * $num2;
    }
}
 
class Calc2 implements ICalc {
    public function sum(float $num1, float $num2): float {
        if ($num1 < 0 || $num2 < 0) {
            throw new \Exception("Input values must be higher than 0");
        }

        return $num1 * $num2;
    }
}

class Calc3 implements ICalc {
    public function sum(float $num1, float $num2): float {
        return round($num1 * $num2, 2);
    }
}

Yukarıdaki örnek interface ve sınıflara baktığımızda temelde 2 farklı float tipinde veri aldığı ve sonucunda yine float döneceğini görüyoruz. Burada alt sınıfları interface üzerinden bağlayarak giriş ve çıkış veri tiplerinin aynı olacağını garanti edebildik fakat metod içerisindeki davranışsal değişiklikler ile bu prensibe nasıl aykırı kod yazılırı görmüş olacağız. Şimdi sınıflarımızı ele alalım;

Calc1 sınıfı ona verdiğimiz 2 sayıyı çarpıp direk olarak sonucu dönüyor.

Calc2 sınıfı ona verdiğimiz 2 sayıdan herhangi birinin 0 dan küçük olmasına izin vermiyor ve Exception fırlatıyor.

Calc3 sınıfımız ise aslında Calc1‘e oldukça yakın davranıyor fakat çıktı verirken virgülden sonra 2 basamak olacak şekilde yuvarlayıp bir çıktı veriyor.

Bu 3 sınıf ICalc interface implement etmiş olsa da aynı tipte giriş alıp, aynı tipte çıktı veriyor gibi görünselerde içlerindeki işleyiş farklılıklarından dolayı birbirleri yerine geçebilir durumda değillerdir. Düşünsenize Calc1 verdiğiniz yeri Calc2 ile değiştirdiğimizi; kodumuz 0 dan küçük bir rakam gelene kadar sorunsuz çalışacaktır fakat ne zaman iki rakamdan birisi 0 dan küçük olursa işte o zaman beklenmedik bir Exception fırladığını görürüz. Siz de Calc2 ile Calc3 değişikliğinde ne gibi sorunlar oluşacağını kendinizce yanıtlayıp cevabınızın doğruluğunu teyit etmek isterseniz yorum/mesaj atabilirsiniz 🙂

Temelde şu kontrolleri sağlamamız ile yerine geçme prensibine uygun bir kodumuz olduğunu canımız yanmadan teyit edebiliriz; türetilmiş sınıfta yeni istisnalar oluşmamalıdır, giriş ve dönüş parametreleri değişmemelidir {tip, sıralama ya da sayı olarak} (hatta mümkünse interface ile sınırlandırılmalıdır), sabitler (constants) mümkün mertebe korunmalı ve mümkünse miras alınmalıdır, üst sınıfta yapılan önemli eylemler sürdürülmelidir (örn: db bağlantısı kapatma vs.), üst sınıf içerisinde private tanımlanmış varlıkların değerleri reflection vs. yöntemler ile değiştirilmemeli gerekiyorsa yeni varlıklar oluşturulmalıdır.

Inherit ettiğiniz sınıflardan beklenmedik exceptionlar fırlamadığı akıl dostu projelerde görüşmek üzere…