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. 🙂

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.

Gökhan Gökalp

View Comments

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

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

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

  • 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.

  • 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.

  • 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?

  • 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.

Recent Posts

Securing the Supply Chain of Containerized Applications to Reduce Security Risks (Policy Enforcement-Automated Governance with OPA Gatekeeper and Ratify) – Part 2

{:tr} Makalenin ilk bölümünde, Software Supply Chain güvenliğinin öneminden ve containerized uygulamaların güvenlik risklerini azaltabilmek…

6 months ago

Securing the Supply Chain of Containerized Applications to Reduce Security Risks (Security Scanning, SBOMs, Signing&Verifying Artifacts) – Part 1

{:tr}Bildiğimiz gibi modern yazılım geliştirme ortamında containerization'ın benimsenmesi, uygulamaların oluşturulma ve dağıtılma şekillerini oldukça değiştirdi.…

8 months ago

Delegating Identity & Access Management to Azure AD B2C and Integrating with .NET

{:tr}Bildiğimiz gibi bir ürün geliştirirken olabildiğince farklı cloud çözümlerinden faydalanmak, harcanacak zaman ve karmaşıklığın yanı…

1 year ago

How to Order Events in Microservices by Using Azure Service Bus (FIFO Consumers)

{:tr}Bazen bazı senaryolar vardır karmaşıklığını veya eksi yanlarını bildiğimiz halde implemente etmekten kaçamadığımız veya implemente…

2 years ago

Providing Atomicity for Eventual Consistency with Outbox Pattern in .NET Microservices

{:tr}Bildiğimiz gibi microservice architecture'ına adapte olmanın bir çok artı noktası olduğu gibi, maalesef getirdiği bazı…

2 years ago

Building Microservices by Using Dapr and .NET with Minimum Effort – 02 (Azure Container Apps)

{:tr}Bir önceki makale serisinde Dapr projesinden ve faydalarından bahsedip, local ortamda self-hosted mode olarak .NET…

2 years ago