Skip to content

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

Open Closed prensibinden sonra vermiş olduğum uzun bir aranın arından sıradaki prensibimiz olan Liskov’un yerine geçme prensibi (Liskov Substitution Principle)  ile makalemize devam edelim. 🙂

Önemli prensipler arasında olan LSP özünde bize geleceğe dönük olarak nasıl hamleler ile kod geliştiriliri veriyor. Önce bu cümleye bulmuş olduğum güzel bir resim ile giriş yapalım. 🙂

liskov

Eğer bir ördek ise, ördek gibi ses çıkartır(Quicklemek her nasıl yazılıyorsa sesi :)) ama bir pile ihtiyacı vardır. Buda demek oluyor ki bir yerlerde yanlış abstraction gerçekleştirmişiz.

LSP prensibi Open Closed prensibinin özel bir türüdür desek yanlış olmaz. OCP’de de olduğu gibi LSP de de genişlemeye açık yapılar söz konusudur. Her ne kadar anlaşılması biraz zor olsa da LSP ilk bakışta, altında yatan ana fikri: alt sınıflardan oluşan nesnelerin üst sınıfın nesneleri ile yer değiştirdikleri zaman, aynı davranışı sergilemesini beklemektir.

Basit bir örnek ile hemen inceleyelim:

Örneğimizde soyut(abstract) olarak bir Car sınıfını tanımlıyoruz ve içerisine Run ve OpenAirConditioning metotlarını ekliyoruz.

namespace LiskovSubstitutionPrinciple
{
    public abstract class Car
    {
        public string Run()
        {
            return "Araba çalıştırıldı.";
        }

        public abstract string OpenAirConditioning();
    }
}

Kalıtım alnacak Run metotu ile araba çalıştırılacak, soyut(abstract) olarak tanımlanmış OpenAirConditioning metotu ilede kalıtım alan türe göre klima açılacak.

Ardından Ferrari ile Murat131 somut sınıflarını (concreate) Car soyut sınıfından kalıtım yoluyla oluşturuyoruz. Burada dikkatin çekilmesi gereken nokta: Murat131’in klima özelliğinin olmamasından dolayı ve soyut(abstract) olarak tanımlanmış metotu override etmek zorunda olduğumuz için ya NotImplementedException hatası fırlatılacak yada null geçerek hatanın üzerini örtmüş olacağız.

    public class Ferrari : Car
    {
        public override string OpenAirConditioning()
        {
            return "Klima açıldı.";
        }
    }

    public class Murat131 : Car
    {
        public override string OpenAirConditioning()
        {
            throw new NotImplementedException();

            //return null;
        }
    }

Buraya kadar her şey güzel. Murat131’in kliması olsun olmasın ben null geçtim ve hataya engel oldum diye düşünüyoruz. Ne olabilir ki? Evet çok şey olabilir! Eğer büyük bir proje ekibi ile aynı proje üzerinde çalışıyorsak ve günlerden bir gün bir kodu başka bir yazılımcı ihtiyaç duyuyorsa ve şöyle bir şeyler kodlamaya başlarsa:

        static void Main(string[] args)
        {
            Car car = new Ferrari();

            car.Run();
            car.OpenAirConditioning();
            // Sıkıntı yok her şey yolunda.

            car = new Murat131();

            car.Run();
            car.OpenAirConditioning(); // ?
        }

Ferrari için her şey güllük gülistanlık olurken aynı davranış Murat131 için olmayacaktır ve o ekip arkadaşınızdan hiç de hoş olmayan laflar duyabilirsiniz helede bir test ekibiniz yoksa ve ürününüzü production ortamına çıkartıyorsanız vay halinize…

LSP’ye girişteki ilk sözü hemen hatırlayalım tekrar: alt sınıflardan oluşan nesnelerin üst sınıfın nesneleri ile yer değiştirdikleri zaman, aynı davranışı sergilemesini beklemektir.

Bu durumda Murat131 aynı davranışı sergilememesinden dolayı LSP’ye uymadığını söyleyebiliriz. Ayrıca, OpenAirConditioning özelliğini temel sınıfımız olan Car sınıfından bir Interface(arayüz) aracılığı ile ayırabilir ve ilgili somut sınıfa (concreate class) implemente ederek bu problemin önüne geçmiş olabiliriz. Hatırlayalım: interface’de genelde can-do ilişkisi vardı, bir edinim kazandırma.

Hemen klima için olan IAirConditionable arayüzünü(interface) oluşturalım ve ilgili sınıfa implemente edelim.

Not: interface’ler genelde başlarında I takısı ve sonunda -able takısı almaktadır. -ebilen gibi bir edinim kazandırma durumlarında.

using System;

namespace LiskovSubstitutionPrinciple
{
    public interface IAirConditionable
    {
        string OpenAirConditioning();
    }

    public abstract class Car
    {
        public string Run()
        {
            return "Araba çalıştırıldı.";
        }
    }

    public class Ferrari : Car, IAirConditionable
    {
        public string OpenAirConditioning()
        {
            return "Klima açıldı.";
        }
    }

    public class Murat131 : Car
    {
    }

}

Klima özelliğini sadece Ferrari için implemente ettiğimizden dolayı, hiç kimse Murat131 için OpenAirConditioning metotuna erişemiyecek ve herhangi bir problem ile karılaşılmayacaktır.

Başka makalelerde görüşmek dileğiyle.

Published inTasarım Prensipleri (Design Principles)

14 Comments

  1. Emre Celebi Emre Celebi

    Makaleleriniz gerçekten açıklayıcı, bu yüzden son 2 prebsibinde makalelerini bekliyorum 🙂

  2. Ayşenur Bupdaylıgil Ayşenur Bupdaylıgil

    Gerçekten açıklayıcı ve gayet anlaşılır bir makale olmuş. Teşekkürler:))

  3. Rıdvan Rıdvan

    Yıl 2019 ve hala birileri faydalanıyor. Eser bırakmak bu olsa gerek. Emeğinize minnettarım

  4. Bilal TÜRK Bilal TÜRK

    Açıklamanız gerçekten mükemmel bilmediğim bir konuyu bu kadar kısa sürede hem akılda kalıcı hemde işime uyarlayabilecek seviyede öğretmek işinin ehli olmakdan geliyor olsa gerek 🙂 Diğer 2 prensibide bekliyorum.

  5. Sinan Sinan

    Merhaba,
    Anlatımız gayet açıklayıcı olmuş. Emeğinize sağlık.

    Ancak benim aklıma takılan bir soru var. Yukarda anlattığınız LSP prensibi ile “Interface Segregation” arasında çok benzerlik var. IS prensibinde de implemente olunan tüm methodları bi alt sınıf kullanmak zorunda, kullanmıyorsa ona özel Interface oluşturup yolumuza devam ediyorduk. Buradaki de aynı mantık gibi geldi bana, tam olarak farkı nedir?

    • Merhaba, kusura bakmayın geç cevap için yeni fırsat bulabildim. Böyle düşünmeniz gayet normal. Eğer özünde sadece “yapılan/implemente edilen işe bakarsak” haklısınız. Tıpkı GOF pattern’larındaki strategy pattern ile factory pattern implementasyonu gibi. Oysa LSP’yi kırdığımızda, aslında Open-closed ile de sıkı sıkıya ilişkilidir. Ben LSP’ye özünde, bir kütüphaneyi kullanan client ve daha çok inheritance gözünden bakıyorum. Yani implemente ettiğim, kullandığım sınıf her ne olursa olsun kendisine yüklenmiş/inherit edilmiş davranışı, beni kırmadan yerine getirmelidir. Makaledeki örnekte de verdiğim gibi, client gözünden bakarsanız üst sınıf olan “Car” ile, concrete’i olan “Murat131” yer değiştiğinde, “OpenAirConditioning” davranışı beklendiği gibi çalışmayacaktır. Burada gizliden open-closed’ı da kırmış oluyorsunuz.

      IS’de ise, inheritance’dan çok, business logic ile yani client’ın nasıl communication kuracağı ile ilgilenmektedir. Umarım anlatabilmişimdir.

  6. ali ali

    Anlamadığım şey şu. Nesneyi oluştururken şöyle oluşturdunuz :

    Car car = new Ferrari();

    Peki bunu bu şekilde de oluşturamaz mıydınız :

    Ferrar car = new Ferrari();

    Bu iki kullanımın farkı var mıdır? Varsa nedir?

  7. isa isa

    Selamlar.

    Bu interface segregation prensibi değil mi ? Kafam karıştı biraz. Farklı neler açıklayabilir misiniz ?

    • Teşekkürler yorumunuz için.

      @Sinan ın yorumuna verdiğim cevabı okuyabilirsiniz.

  8. Ferit Ferit

    Sene 2020 Halen insanlar yararlanıyor Teşekkürler

Leave a Reply to ali Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.