SOLID Prensipleri

Liskov Substitution Principle

L — Liskov Yerine Geçme Prensibi

Amaç: Alt sınıflar, üst sınıflarının yerine sorunsuzca kullanılabilmelidir. Üst sınıfla çalışan kod, alt sınıf verildiğinde de doğru çalışmaya devam etmelidir.

1

Basit Anlatım

Teknik terim yok — günlük hayattan bir benzetme

Bir araç kiralama şirketinden "bir otomobil" kiraladınız. Size hangi marka ve model gelirse gelsin, şunları beklersiniz: direksiyonu çevirince döner, frene basınca durur, gaza basınca hızlanır.

Şimdi düşünün: size "oyuncak araba" verdiler. Evet, o da bir "araba"; ama içine binemezsiniz. Ya da size verilen araç frene basınca hızlanıyor. Kâğıt üzerinde araba ama beklediğiniz gibi davranmıyor. İşte bu Liskov ihlalidir.

Ördek testi

"Ördek gibi görünüyor, ördek gibi vaklıyor, ama pil istiyorsa… muhtemelen yanlış soyutlamaya sahipsiniz." Bir oyuncak ördek, ördek gibi görünür ama gerçek bir ördeğin yerine geçemez: göle bırakırsanız yüzmez, acıkmaz, pili biter.

Liskov Prensibi şunu der: "Bir şeyin yerine başka bir şey koyduğunda, onu kullanan kişi farkı fark etmemeli ve hiçbir şey bozulmamalı."
2

Teknik Anlamı

Bir yazılımcının bakış açısıyla

Barbara Liskov'un 1987'de tanımladığı prensibe göre: S, T'nin bir alt tipiyse, T tipindeki nesneler programın doğruluğu bozulmadan S tipindeki nesnelerle değiştirilebilmelidir.

Kalıtım "is-a" (bir türüdür) ilişkisi kurar. Ancak gerçek dünyadaki "is-a" ilişkisi, kodda davranışsal olarak her zaman geçerli değildir. Matematikte her kare bir dikdörtgendir; ama kodda Square : Rectangle yazmak LSP'yi ihlal eder.

Alt sınıfın uyması gereken kurallar (Design by Contract)

İhlal belirtileri

.NET'ten gerçek bir örnek

Diziler IList<T> arayüzünü uygular; ama ((IList<int>)new int[3]).Add(5) çağrısı NotSupportedException fırlatır. IList<T> ile çalışan bir metot, dizi verildiğinde beklenmedik şekilde çöker. Bu, framework'teki bilinen bir LSP ihlalidir.

3

C# ile Uygulama

Adım adım, çalışan kod örneği

Senaryo: Meşhur Dikdörtgen–Kare problemi. Önce ihlali ve neden olduğu hatayı göreceğiz, sonra doğru tasarımı.

Adım 1 YANLIŞ Kare, dikdörtgenden türetiliyor

Kare her iki kenarı eşit tutmak için setter'ları override ediyor. Matematiksel olarak mantıklı görünüyor, ama dikdörtgenin davranış sözleşmesini bozuyor: "Genişliği değiştirmek yüksekliği etkilemez."

public class Rectangle
{
    public virtual int Width { get; set; }
    public virtual int Height { get; set; }
    public int Area => Width * Height;
}

public class Square : Rectangle
{
    public override int Width
    {
        get => base.Width;
        set { base.Width = value; base.Height = value; }   // sürpriz yan etki!
    }
    public override int Height
    {
        get => base.Height;
        set { base.Width = value; base.Height = value; }
    }
}

public static class AreaTests
{
    // Rectangle için yazılmış, tamamen makul bir metot:
    public static void Resize(Rectangle r)
    {
        r.Width = 5;
        r.Height = 4;
        string result = r.Area == 20 ? "DOĞRU" : "HATALI!";
        Console.WriteLine($"  {r.GetType().Name,-9} alan = {r.Area,2} (beklenen 20) -> {result}");
    }
}

Adım 2 DOĞRU Ortak soyutlama, bağımsız şekiller

Kare ve dikdörtgen birbirinden türemiyor; ikisi de IShape sözleşmesini eksiksiz yerine getiriyor. Nesneler immutable olduğu için "bir kenarı değiştirince diğeri de değişti" gibi sürprizler mümkün değil.

public interface IShape
{
    string Name { get; }
    double Area { get; }
}

public sealed record Rectangle(double Width, double Height) : IShape
{
    public string Name => $"Dikdörtgen {Width}x{Height}";
    public double Area => Width * Height;
}

public sealed record Square(double Side) : IShape
{
    public string Name => $"Kare {Side}x{Side}";
    public double Area => Side * Side;
}

public sealed record Circle(double Radius) : IShape
{
    public string Name => $"Daire r={Radius}";
    public double Area => Math.PI * Radius * Radius;
}

Adım 3 DOĞRU İstemci: her IShape'e güvenle çalışır

public static class ShapeReport
{
    public static void Print(IEnumerable<IShape> shapes)
    {
        foreach (var s in shapes)
            Console.WriteLine($"  {s.Name,-16} alan = {s.Area,7:0.00}");
        Console.WriteLine($"  Toplam alan: {shapes.Sum(s => s.Area):0.00}");
    }
}

Adım 4 Kullanım

Tüm parçaları bir araya getiren istemci (client) kodu:

Console.WriteLine("İhlal eden tasarım:");
Kotu.AreaTests.Resize(new Kotu.Rectangle());
Kotu.AreaTests.Resize(new Kotu.Square());     // Rectangle yerine geçemedi!

Console.WriteLine("\nDoğru tasarım:");
ShapeReport.Print(new IShape[] { new Rectangle(5, 4), new Square(4), new Circle(1) });

Console.WriteLine("\n.NET'teki gerçek ihlal:");
IList<int> list = new int[3];
try { list.Add(5); }
catch (NotSupportedException) { Console.WriteLine("  int[] bir IList ama Add() desteklemiyor!"); }

Konsol çıktısı

İhlal eden tasarım:
  Rectangle alan = 20 (beklenen 20) -> DOĞRU
  Square    alan = 16 (beklenen 20) -> HATALI!

Doğru tasarım:
  Dikdörtgen 5x4   alan =   20,00
  Kare 4x4         alan =   16,00
  Daire r=1        alan =    3,14
  Toplam alan: 39,14

.NET'teki gerçek ihlal:
  int[] bir IList ama Add() desteklemiyor!

Kodda dikkat edilmesi gerekenler

Özetle: Alt sınıflar, üst sınıfın sözleşmesine (davranış vaatlerine) tamamen uymalıdır; aksi halde polimorfizm güvenilmez hale gelir.
← Ana sayfaya dön