SOLID- Liskov Substitution Principle


Open / Closed Prensip  Bir önceki yazıda açıklandığı, sağlam sürdürülebilir ve yeniden kullanılabilir yazılım bileşenlerini yazmaya olanak sağlayan OOP'deki anahtar kavramlardan biridir. Ancak, bu ilkenin kurallarına uymak, sisteminizin bir bölümünü diğer bölümlerini bozmadan değiştirebilmeniz için yeterli değildir. Sınıflarınız ve arayüzleriniz, yan etkilerden kaçınmak için Liskov Substitution Prensibini takip etmelidir.

Liskov Substitution Principle Robert C. Martin'in ünlü SOLID tasarım ilkelerinin 3.'sü :

Üst sınıfın ve alt türlerinin davranışlarına odaklanarak Open / Closed İlkesini genişletir. Bu makalede size göstereceğim gibi, bu en azından Open /Closed İlkenin yapısal gerekliliklerini doğrulamak açısından önemli ancak daha da zordur.

Liskov Substitution Principle Tanımı

Liskov İkame ilkesi, 1987'de “Data abstraction” konulu konferansında Barbara Liskov tarafından tanıtıldı.

Pratik yazılım geliştirmede Liskov Substitution Prensibi

Bir üst sınıfa ait nesnelerin, uygulamayı bozmadan alt sınıflarının nesneleri ile değiştirilebileceğini tanımlar. Bu, alt sınıflarınızın nesnelerinin, base sınıfınızın nesneleriyle aynı şekilde davranmasını gerektirir. Bunu, Bertrand Meyer tarafından tanımlanan  design by contract konseptine göre oldukça benzer birkaç kurala uyarak yapabilirsiniz .

Alt sınıfın geçersiz kılınan yönteminin, üst sınıfın yöntemi olarak aynı girdi parametresi değerlerini kabul etmesi gerekir. Bu, daha az kısıtlayıcı geçerlilik kuralları uygulayabileceğiniz anlamına gelir, ancak alt sınıfınızda daha sıkı olanları zorlamanıza izin verilmez. Aksi takdirde, bu yöntemi base sınıfın bir nesnesinde çağıran kodlar , alt sınıfın nesnesiyle çağrılırsa bir istisnaya neden olabilir.

Benzer kurallar, yöntemin dönüş değeri için geçerlidir. Alt sınıfın bir yönteminin dönüş değeri, üst sınıfın yönteminin dönüş değeriyle aynı kurallara uymak zorundadır. Tanımlanmış iade değerinin belirli bir alt sınıfını döndürerek veya üst sınıfın geçerli döndürme değerlerinin bir alt kümesini döndürerek yalnızca daha katı kurallar uygulamanıza karar verebilirsiniz.

Bu prensibi kodunuza uygulamaya karar verirseniz, sınıflarınızın davranışları yapısından daha önemli hale gelir. Ne yazık ki, bu ilkeyi uygulamak için kolay bir yol yoktur

Kodunuzun Liskov Prensibine uyduğundan emin olmak için kendi kontrollerinizi uygulamanız gerekmektedir. En iyi durumda, bunu kod incelemeleri ve test senaryoları aracılığıyla yaparsınız. Test durumlarınızda, uygulamanızın belirli bir bölümünü, bunların hiçbirinin bir hataya neden olmadığından veya performansını önemli ölçüde değiştirdiğinden emin olmak için tüm alt sınıfların nesneleriyle çalıştırabilirsiniz. Bir kod incelemesi sırasında benzer kontroller yapmayı deneyebilirsiniz. Fakat daha da önemlisi, gerekli tüm test senaryolarını oluşturup çalıştırdığınızı kontrol etmenizdir.

Basit Bir Örnek

Bu prensibi göstermek için klasik Circle(Daire)-Ellipse problemini kullanacağız. Herhangi bir elipsin alanını bulmamız gerektiğini hayal edelim. Yani, bir elipsi temsil eden bir sınıf oluştururuz:

public class Ellipse

    {

        public double MajorAxis { get; set; }

        public double MinorAxis { get; set; }

 

        public virtual void SetMajorAxis(double majorAxis)

        {

            MajorAxis = majorAxis;

        }

 

        public virtual void SetMinorAxis(double minorAxis)

        {

            MinorAxis = minorAxis;

        }

 

        public virtual double Area()

        {

            return MajorAxis * MinorAxis * Math.PI;

        }

    }

Lisede gördüğümüz geometriden, bir çemberin elips için özel bir durum olduğunu biliyoruz, bu yüzden Ellipse'den miras alan bir Circle sınıfı yaratıyoruz, ancak SetMajorAxis her iki ekseni de ayarlıyor (çünkü bir daire içinde büyük ve küçük eksenler her zaman aynı olmalı , bu sadece yarıçaptır)

public class Circle : Ellipse

    {

        public override void SetMajorAxis(double majorAxis)

        {

            base.SetMajorAxis(majorAxis);

            this.MinorAxis = majorAxis; //In a cirle, each axis is identical

        }

    }

Her iki ekseni de ayarlarsak, alanı hesaplamaya çalışmak yanlış sonuç verir.

Circle circle = new Circle();

    circle.SetMajorAxis(5);

circle.SetMinorAxis(4);

var area = circle.Area(); //5*4 = 20

Bu Liskov İkame İlkesinin ihlalidir. Ancak, bu kodu yeniden düzenlemenin en iyi yolu, birkaç olasılık olduğu için bariz değildir. Bir çözüm de Circle'ın SetMinorAxis'i uygulamak için olması olabilir.

public class Circle : Ellipse

    {

        public override void SetMajorAxis(double majorAxis)

        {

            base.SetMajorAxis(majorAxis);

            this.MinorAxis = majorAxis; //In a cirle, each axis is identical

        }

 

        public override void SetMinorAxis(double minorAxis)

        {

            base.SetMinorAxis(minorAxis);

            this.MajorAxis = minorAxis;

        }

 

        public override double Area()

        {

            return base.Area();

       }

    }

 

Genel olarak daha az kod içeren bir başka çözüm de, Çember'i tamamen ayrı bir sınıf olarak ele almak olabilir.

public class Circle

    {

        public double Radius { get; set; }

        public void SetRadius(double radius)

        {

            this.Radius = radius;

        }

 

        public double Area()

        {

            return this.Radius * this.Radius * Math.PI;

        }

    }

Her iki çözümün de kendine ait dezavantajları vardır. Birincisi,   Circle sınıfında esas olarak aynı şeyi yapan iki farklı yönteme sahibiz. İkincisi, Elips'in özel bir durumu olmasına rağmen, Circle'ı ayrı bir sınıf gibi ele aldığımız için uygunsuz modelleme olarak düşünülebilir. Yine de seçim göz önüne alındığında, daha iyi bir genel model sağladığını düşündüğümden (ve gereksiz bir kod kullanmıyorsa), kişisel olarak, birinciden ziyade ikinci çözümü seçmem daha olasıdır.

Buna değer mi?

 Diğer SOLID prensiplerinde olduğu gibi, gerçek dünyanın her zaman mükemmel bir şekilde modellememize izin vermesini bekleyemeyiz. Bununla birlikte, LSP, hiyerarşiler üzerinde işlevselliği sürdürmede çok yararlıdır ve bu amaçla, özellikle uygundur.