Liskov Substitution Principle (LSP)¶
Alt sınıflar, üst sınıfların yerine sorunsuz kullanılabilmelidir; davranış kontratını bozan kalıtım hiyerarşileri beklenmeyen hatalara yol açar.
1. NotImplementedException Fırlatmak¶
❌ Yanlış Kullanım: Interface metodunu uygulamayıp exception fırlatmak.
public interface IFileStorage
{
Task UploadAsync(Stream file);
Task<Stream> DownloadAsync(string path);
Task DeleteAsync(string path);
}
public class ReadOnlyStorage : IFileStorage
{
public Task UploadAsync(Stream file) => throw new NotImplementedException();
public Task<Stream> DownloadAsync(string path) { /* ... */ }
public Task DeleteAsync(string path) => throw new NotImplementedException();
}
✅ İdeal Kullanım: Interface’leri sorumluluk bazında ayırın.
public interface IFileReader
{
Task<Stream> DownloadAsync(string path);
}
public interface IFileWriter
{
Task UploadAsync(Stream file);
Task DeleteAsync(string path);
}
public class ReadOnlyStorage : IFileReader
{
public Task<Stream> DownloadAsync(string path) { /* ... */ }
}
public class FullStorage : IFileReader, IFileWriter
{
public Task<Stream> DownloadAsync(string path) { /* ... */ }
public Task UploadAsync(Stream file) { /* ... */ }
public Task DeleteAsync(string path) { /* ... */ }
}
2. Precondition’ları Güçlendirmek¶
❌ Yanlış Kullanım: Alt sınıfta üst sınıftan daha katı kurallar koymak.
public class Rectangle
{
public virtual void SetWidth(int width)
{
if (width <= 0) throw new ArgumentException();
Width = width;
}
}
public class RestrictedRectangle : Rectangle
{
public override void SetWidth(int width)
{
if (width <= 0 || width > 100) // Üst sınıfta olmayan ek kısıtlama
throw new ArgumentException();
Width = width;
}
}
✅ İdeal Kullanım: Alt sınıf üst sınıfın kontratına sadık kalsın.
public class Rectangle
{
public virtual void SetWidth(int width)
{
if (width <= 0) throw new ArgumentException();
Width = width;
}
}
public class RestrictedRectangle : Rectangle
{
public override void SetWidth(int width)
{
base.SetWidth(width); // Üst sınıf kontratını korur
// Ek iş mantığı
}
public int MaxWidth => 100;
public bool IsWithinLimit => Width <= MaxWidth;
}
3. Postcondition’ları Zayıflatmak¶
❌ Yanlış Kullanım: Üst sınıfın garanti ettiği davranışı alt sınıfta bozmak.
public class OrderRepository
{
public virtual async Task<Order> GetByIdAsync(int id)
{
return await _context.Orders.FindAsync(id)
?? throw new NotFoundException($"Sipariş bulunamadı: {id}");
}
}
public class CachedOrderRepository : OrderRepository
{
public override async Task<Order> GetByIdAsync(int id)
{
return _cache.Get<Order>(id); // Cache'de yoksa null dönebilir!
}
}
✅ İdeal Kullanım: Alt sınıf üst sınıfın garanti ettiği davranışı korusun.
public class CachedOrderRepository : OrderRepository
{
public override async Task<Order> GetByIdAsync(int id)
{
var cached = _cache.Get<Order>(id);
if (cached != null) return cached;
var order = await base.GetByIdAsync(id); // NotFoundException garantisi korunur
_cache.Set(id, order);
return order;
}
}
4. Rectangle-Square Problemi¶
❌ Yanlış Kullanım: Kare’yi dikdörtgenden türetmek.
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
{
set { base.Width = value; base.Height = value; }
}
public override int Height
{
set { base.Width = value; base.Height = value; }
}
}
// Beklenmeyen davranış
Rectangle rect = new Square();
rect.Width = 5;
rect.Height = 3;
// Area 15 beklenirken 9 döner
✅ İdeal Kullanım: Ortak interface ile soyutlayın, kalıtımdan kaçının.
public interface IShape
{
int Area { get; }
}
public class Rectangle : IShape
{
public int Width { get; init; }
public int Height { get; init; }
public int Area => Width * Height;
}
public class Square : IShape
{
public int Side { get; init; }
public int Area => Side * Side;
}
5. Collection Kalıtımında LSP İhlali¶
❌ Yanlış Kullanım: Readonly collection’ı writable collection’dan türetmek.
public class ReadOnlyProductList : List<Product>
{
public new void Add(Product item) => throw new NotSupportedException();
public new void Remove(Product item) => throw new NotSupportedException();
}
// LSP ihlali: List<Product> bekleyen kod çalışmaz
void AddProducts(List<Product> products)
{
products.Add(new Product()); // NotSupportedException!
}
✅ İdeal Kullanım: Doğru soyutlama seviyesinde interface kullanın.
public class ProductCatalog
{
private readonly List<Product> _products = new();
public IReadOnlyCollection<Product> Products => _products.AsReadOnly();
public void Add(Product product) => _products.Add(product);
}
// IReadOnlyCollection kullanarak tüketiciye doğru kontratı sunun
void DisplayProducts(IReadOnlyCollection<Product> products)
{
foreach (var product in products)
Console.WriteLine(product.Name);
}