Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

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

Liskov'un Yerine Geçme Prensibi(Liskov Substitution Principle - LSP)

Merhaba,

Önceki yazılarımda SOLID prensiplerinden STek Sorumluluk Prensibi(Single Responsibility Principle – SRP)‘ni ve OAçık Kapalı Prensibi(Open Closed Principle – OCP) incelemiştik. Bu yazımızda ise sıradaki L timsaline denk gelen Liskov’un Yerine Geçme Prensibi(Liskov Substitution Principle – LSP) izah edeceğim.

Liskov’un Yerine Geçme Prensibi, diğerlerine göre anlaşılması en zor prensiptir diyebilirim. Barbara Liskov adlı hanımefendi tarafından 1988 yılında ‘Data Abstraction and Hierarchy’ isimli kitabında bu prensip gündeme getirilmiştir.

Her zor konuda olduğu gibi haliyle bu konuda “aslınnda anlaşıldığında çook basitt” diyeceğimiz bir konudur. Lakin bu konuyu zorlaştıran Barbara Liskov’un konuyla alakalı yaptığı açıklamadır. Bunun yanında 2. ve 3. kaynakların konuyu daha basit seviyede anlaşılır bir üslupla ele almalarıdır. Ee son olarakda bizim nam-ı diğer Türkler’in üzerine cila çekmesidir.

Tabi yukarıdaki paragrafta anlattığım durum, kitaplardan ya da makalelerden konuyu okuyarak tek başına öğrenme gayretinde bulunanlar içindir. Eğer alanında uzman bir eğiticiden 1. dereceden eğitim alarak ilgili konuya fakıf olmuşsanız burada yazdıklarıma hak vermemeniz gayet doğal olacaktır.

Velhasıl, bu zorlu ama anlaşılmayı bekleyen konuyu bu makale sonunda net bir şekilde idrak edeceğinizi umuyorum. Tabi öncelikle kitaplarda ve konuyla ilgili makalelerde kafamızı karıştıran tanımlamalara ve yorumlara yer vererek konuya giriş yapıyorum.

Nedir Bu Liskov’un Yerine Geçme Prensibi

LSP’yi orataya atan Barbara Liskov öncülüğünde bir çok kişi ilgili konuyu açıklamaya çalışmış, doğal olarak farklı yaklaşımlardan dolayı farklı izahlar ortaya çıkmıştır. Şimdi bunların bir kısmına göz atalım.

Barbara Liskov’a göre;

Aranan yer değiştirme özelliği şöyle tanımlanabilir: T cinsinden parametre alan tüm programlar (fonksiyonlar) P olacak şekilde, S tipinde o1 nesnesi ve T tipinde o2 nesnesi olsun. Eğer o1 ile o2 nesneleri yer değiştirdiğinde P’nin davranışı değişmiyorsa S tipi T tipinin alt tipidir!

Evet, bende bişey anlamadım.

Bilimsel olarak matematik ile formülüze bir şekilde LSP’ye açıklama getirmişler. O açıklamaya göreyse;

q(x) fonksiyonu T tipindeki x nesnesi için kanıtlanabilirdir. O halde T tipinin alt tipi olan S tipindeki y nesnesi için de q(y) fonksiyonu kanıtlanabilir olmalıdır.

Evet, bundanda bişey anlamadığımız aşikar.

‘Uncle Bob’ lakaplı Robert Cecil Martin ise LSP’yi daha anlaşılır bir vaziyette aşağıdaki gibi izah etmiştir;

Temel sınıfın(base class) işaretçisini(pointer) ya da referansını kullanan fonksiyonlar, bu sınıftan türemiş olan sınıfları(derived class) da ekstra bilgiye ihtiyaç duymaksızın kullanabilmelidir.

Ehhh… Biraz daha yazılımcıya uygun bir açıklama olmuş gibi. En azından base classlar derived classlar derken kafada bişeylerin hayalini kurabiliyoruz. O kadar literatür taradım, kaynak okudum ama şu yukarıdaki açıklamanın bana polimorfizmi anımsattığı kadar kimse gıram bahsetmemiş. Haliyle algıladığımdan şüphelenir oldum. Eğer kaçırdığım bir mana, bir kelime yahut edat bağlaç varsa haber verinde neden yanlış anlamışım farkına varmama yardımcı olun.

Siz bana Uncle Bob’un sözündeki algı hatamı izah edene kadar benim şu kanaatte olduğumu bilmenizi isterim ki, bu yukarıdaki açıklamaların hiçbirinden bir cacık olmaz! Anlamak için anlaşılır bir açıklama gerek. Koskoca prensibi batı algısıyla özetleyecez derken aman dikkat önemsizleştirmeyelim!

Şimdi konuya şahsen girmeden önce bizim Türkler’in konuya getirdikleri açıklamalara göz atalım. (Adreslerini verdiğim yerli yazılımcılar konuyla ilgili araştırma yaparken internette tesadüfen denk geldiklerimdir.)

Ömer İşıker;

Temel sınıfta aşağıdaki sınıflara ait özel fonksiyon vs bulunmamalıdır. Yani temel sınıfta türemiş sınıflardan hiç biri için özel bir şey yazılmış olmamalıdır.

Bu açıklama Ömer İşıker tarafından şu adresteki LSP ile ilgili makalede yer almaktadır. Kanaatimce LSP’ye izahatte bulunmamızı sağlayan yeterli bir açıklama değildir.

Türkay Ürkmez;

Aynı temel sınıftan türeyen tüm sınıflar, birbirlerinin yerine kullanılabilir olmalıdır. Bu yer değiştirme durumunda, sınıfa özel bir istisna kesinlikle oluşmamalıdır.

Şahsi kanaatim, konuya en iyi açıklama getiren Türk yazarımız Türkay Ürkmez’dir. Türkay Ürkmez’in konuyla ilgili makalesi için tıklayınız.

Bir adette ekşi sözlükte konuya izahatte bulunan arkadaşımızın düşüncesini görelim.

yeni bir istek geldi müşteriden, sen de bunu sağlamak için mevcut olan bir class’ten child class üretiyorsun, veyahut method’ları override ediyorsun. mevcut class yerine child class’i kullandığında bir hata oluşmamalı ya da worst case’de çıkan hata, bütün uygulamayı darmadağın etmemeli. türetilmiş sınıflar (ihtiyaç üzerine method’ları override edilmiş ya da extend edilmiş), türetilikleri class’ler ile değiştirilebilir olmalıdır kısaca.

Evet, bu arkadaşımız bayağı uçuk bir izahatte bulunmuşlar. Kanımca LSP’yi ya anlamamış ya da “sttir et LPS’yi… bakın ben terminoloji biliyorum” diye duyurma derdinde. Ha konuyla alakasını sorarsanız eksilerde tabi… Entrye buradan ulaşabilirsiniz.

Daha sürüsüne bereket Türk yazılım geliştirici tarafından ilgili konu ele alınmıştır. Fazla kafa karıştırmak istemediğim için alıntılarda bunlarla yetiniyorum 🙂

Şimdi LSP’yi kendimiz ele alalım.

Barbara Liskov’un ortaya attığı bu prensibe göre üst sınıf ile alt sınıf arasında davranış olarak hiçbir fark olmamalıdır. Yani birbirlerinin yerine kullanılabilmelidirler.

Düşünün ki, arabanıza bir klima düğmesi koydunuz ve onu işlevsiz bıraktınız. Yani görüntüden ibaret ama iş yok. İşte bu durum LSP’ye aykırıdır. LSP, Dummy Code(Sahte kod) dediğimiz bu tarz duruma hayır demektedir. Yani bir düğme varsa işlevide olmalıdır. Buradan yola çıkarsak eğer base classların(kalıtım veren sınıfların), derived classlardaki(kalıtım alan sınıflardaki) işlevselliği tam olarak yerine getirdiğinden emin olmalıyız.

Eee peki ne anlatmaya çalışıyoruz. Bir sınıfta bulunan herhangi bir işlev, kendisinden kalıtım alan sınıflarda kullanılmayacaksa eğer işte bu durum LSP’ye aykırı bir durumdur.

Örnek olarak şöyle bir senaryo çizelim.

Bir ordumuz olsun. Ordumuzdaki uçaklar bir yandan keşife çıkabilsin bir yandan da hedefi vurabilsin.

Liskov'un Yerine Geçme Prensibi(Liskov Substitution Principle - LSP)

    interface IUcak
    {
        bool KesifYap();
        bool HedefiVur();
    }

    class UcakA : IUcak
    {
        public bool HedefiVur()
        {
            Console.WriteLine("UcakA Hedefi vurdu.");
            return true;
        }
        public bool KesifYap()
        {
            Console.WriteLine("UcakA keşfi tamamladı.");
            return true;
        }
    }
    class UcakB : IUcak
    {
        public bool HedefiVur()
        {
            Console.WriteLine("UcakB Hedefi vurdu.");
            return true;
        }
        public bool KesifYap()
        {
            Console.WriteLine("UcakB keşfi tamamladı.");
            return true;
        }
    }
    class UcakC : IUcak
    {
        public bool HedefiVur()
        {
            Console.WriteLine("UcakC Hedefi vurdu.");
            return true;
        }
        public bool KesifYap()
        {
            Console.WriteLine("UcakC keşfi tamamladı.");
            return true;
        }
    }

    class Savas
    {
        List<IUcak> Ucaklar;
        public Savas(List<IUcak> Ucaklar)
        {
            this.Ucaklar = Ucaklar;
        }

        public void KesifYap()
        {
            Ucaklar.ForEach(u =>
            {
                u.KesifYap();
            });
        }

        public void HedefiVur()
        {
            Ucaklar.ForEach(u =>
            {
                u.HedefiVur();
            });
        }
    }

Varsayalım ki, ordumuza yeni bir uçak daha geldi ama sadece keşfe çıkabilmektedir.

    class UcakD : IUcak
    {
        public bool HedefiVur()
        {
            return false;
        }
        public bool KesifYap()
        {
            Console.WriteLine("UcakD keşfi tamamladı.");
            return true;
        }
    }

Yeni uçağımızın hedefi vurma özelliği yoktur. Lakin IUcak arayüzümüz tüm elemanları zorla uygulatmaktadır. Eee haliyle base class var olan HedefiVur metodunu derived classta kullanmayacağız. İşte yeni uçağımız(UcakD) için burada HedefiVur metodu Dummy Code yapısındadır.

Peki “HedefiVur” metodunu UcakD’de kullanmayacaksak aşağıdaki gibi önlem almalıyız.

    class Savas
    {
        .
        .
        .
        public void HedefiVur(
        {
            Ucaklar.ForEach(u =>
            {
                if (!(u is UcakD))
                {
                    u.HedefiVur();
                }
            });
        }
    }

Yukarıdaki işlem dışında UcakD’de HedefiVur çağrıldığı zaman hata fırlatılıp “Savas” sınıfımızda try catch bloklarıyla kontrol sağlanabilir.

Tabi bunlar konumuzun dışında kalan konulardır. Asıl mevzu LSP’yi ilgilendiren base classtaki kullanılmayacak kodu derived class’a zorla implement etmektir. Haliyle LSP’ye aykırı olan bu durum OCP‘ye de aykırıdır. Çünkü her yeni gelen ve hedefi vuramayan uçakta “Savas” sınıfı içerisinde yeni if – else bloklarıyla kontrol sağlamız gerekecektir. Buda değişiklik demektir.

Gelin şimdi bu sorunu Liskov’un Yerine Geçme Prensibini uygulayarak çözelim.
Liskov'un Yerine Geçme Prensibi(Liskov Substitution Principle - LSP)
Class Diyagramında görüldüğü gibi herşey açık ve net. Tasarımımızı LSP’ye uygun bir vaziyette şekillendirebilmek için gördüğünüz gibi base classtaki tüm işlevler farklı arayüzlere bölmüş bulunmaktayım. Bu yaptığımız işlem bir sonraki yazımda ele alacağım arayüz ayrım prensibinede uyarlı bir yaklaşımdır.

“UcakA”, “UcakB” ve “UcakC” hedefi vurma ve keşif yapma özelliklerine sahip oldukları için iki arayüzden de kalıtım almaktadırlar. “UcakD” ise sadece keşif yapabileceği için sadece “IUcakKesif” arayüzünden kalıtım almaktadır. Bu sayede kullanmayacağı bir işlev olan HedefiVur metodunu barındırmak zorunda kalmayacak ve Dummy Code durumunuda ortadan kaldırmış olacağız.

    interface IUcakKesif
    {
        bool KesifYap();
    }

    interface IHedefiVur
    {
        bool HedefiVur();
    }

    class UcakA : IUcakKesif, IHedefiVur
    {
        public bool HedefiVur()
        {
            Console.WriteLine("UcakA Hedefi vurdu.");
            return true;
        }
        public bool KesifYap()
        {
            Console.WriteLine("UcakA keşfi tamamladı.");
            return true;
        }
    }
    class UcakB : IUcakKesif, IHedefiVur
    {
        public bool HedefiVur()
        {
            Console.WriteLine("UcakB Hedefi vurdu.");
            return true;
        }
        public bool KesifYap()
        {
            Console.WriteLine("UcakB keşfi tamamladı.");
            return true;
        }
    }
    class UcakC : IUcakKesif, IHedefiVur
    {
        public bool HedefiVur()
        {
            Console.WriteLine("UcakC Hedefi vurdu.");
            return true;
        }
        public bool KesifYap()
        {
            Console.WriteLine("UcakC keşfi tamamladı.");
            return true;
        }
    }
    class UcakD : IUcakKesif
    {
        public bool KesifYap()
        {
            Console.WriteLine("UcakD keşfi tamamladı.");
            return true;
        }
    }

    class Savas
    {
        List<IHedefiVur> HedefVurucular;
        List<IUcakKesif> KesifYapicilar;
        public Savas(List<IUcakKesif> KesifYapicilar, List<IHedefiVur> HedefVurucular)
        {
            this.KesifYapicilar = KesifYapicilar;
            this.HedefVurucular = HedefVurucular;
        }

        public void KesifYap()
        {
            KesifYapicilar.ForEach(u =>
            {
                u.KesifYap();
            });
        }

        public void HedefiVur()
        {
            HedefVurucular.ForEach(u =>
            {
                u.HedefiVur();
            });
        }
    }

Gördüğünüz gibi “Savas” sınıfı içerisindeki if – else kontrolüne gerek duymamaktayız. Haliyle bir değişiklik değil gelişimsellik söz konusu olduğu için OCP ve her yapı sade ve sadece kendi işini gördüğü için SRP‘ye uygun bir tasarım gerçekleştirmiş olduk.

Evet… Liskov’un Yerine Geçme Prensibinde bu şekilde bir tasarım ve bakış açısı sergilememiz gerekmektedir. 2. sayfada bu prensibi pekiştirmek amacıyla farklı bir örnek senaryo bulacaksınız.

Okuduğunuz için teşekkür ederim…

Sonraki yazılarımda görüşmek üzere…

İyi çalışmalar dilerim…

Bunlar da hoşunuza gidebilir...

12 Cevaplar

  1. Tuğba dedi ki:

    Birkaç sitede araştırma yaptım ama en açıklayıcı olarak sizin anlatımınızı buldum.Ağzınıza sağlık.

  2. Çağlar dedi ki:

    Kardeşim makalelerin çok güzel.
    Başarılarının devamını dilerim.

  3. E. Fatih Ersoy dedi ki:

    Basit, yalın ve açıklayıcı. Bir design pattern öğreticisi için gayet güzel bir design. Tebrikler 🙂

  4. Kadir dedi ki:

    Mükemmel.

  5. caner dedi ki:

    tsk

  6. fatih dedi ki:

    Öncelikle teşekkürler, blogunuzdan çok faydalanıyorum.

    Fakat bu lsp’i sizin makalenizden de anlamadım. Daha doğrusu anladım da bu interface segregation değil mi bu makalede anlattığınız? interface segregation’dan ayırt eden kısmı ne onu anlayamadım.

    https://www.gencayyildiz.com/blog/arayuz-ayrim-prensibiinterface-segregation-principle-isp/

    Teşekkürler.

    • Naci Karakullukçu dedi ki:

      Aslında şöyle bir durum var. Hocamız burada interfaceler üzerinden gösterdiği için kafan oraya kaymış ama bunun yerine normal class veya abstract class’tan miras alsan da aynı şey olurdu. Bu desenin özü; kullanılmayacak bir özelliğin alt sınıfta bulunmaması gerektiğidir. Eğer türeyen sınıf türediği sınıfın her özelliğini kullanmıyorsa ana sınıfı parçalara ayırmalısın. Yapman gereken bu. Alt sınıfta override edip içini boş bırakman durumu çözmüyor. Eğer ihtiyacın yoksa bünyende barınmıyor olması lazım.

      • Mirza dedi ki:

        Çok istifade ettiğim bir seri oldu. Emeklerinize sağlık. BigO Notation Algoritma Analizleri gibi başlıklarda da içeriklerinizin olmasını çok isteriz. Özellikle mülakatlarda bu ve benzeri konulara ihtiyaç olabiliyor.

  7. Barış_D dedi ki:

    @Naci Karakullukçu
    Naci Bey, sizin açıklamanız da daha da açıklayıcı hale getirmiş. Teşekkürler.

  1. 05 Mayıs 2016

    […] Principle – SRP)‘ni, O–Açık Kapalı Prensibi(Open Closed Principle – OCP) ve L-Liskov’un Yerine Geçme Prensibi(Liskov Substitution Principle – LSP) incelemiştik. Bu içeriğimizde ise sıradaki I harfine denk gelen Arayüz Ayrım […]

  2. 17 Eylül 2021

    […] Bu konuyu araştırırken birçok farklı kaynak ve yazı inceledim. Birçok yazıyı ve kaynağı bir arada bulundurduğunu düşündüğüm yazıyı buradan bulabilirsiniz. […]

Tuğba için bir yanıt yazın Yanıtı iptal et

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir